From b096f0797800b1898a125d3357fcf71224cdac1b Mon Sep 17 00:00:00 2001 From: DIVYANSH-675 Date: Wed, 25 Mar 2026 01:21:37 +0530 Subject: [PATCH] Initial commit from ux_aura_central --- .gitea/workflows/feature_branch_autopr.yml | 128 ++ .gitignore | 24 + App.tsx | 12 + Dockerfile | 20 + FIXED_cloudflare_build.yml | 330 +++++ README.md | 20 + References/api_storage.json | 1 + References/manager_api.json | 1 + components/Dashboard.tsx | 106 ++ components/Layout.tsx | 43 + components/SetupPrompt.tsx | 36 + components/icons.tsx | 172 +++ containers/MainContainer.tsx | 44 + containers/layout/MainLayout.tsx | 49 + containers/layout/components/Footer.tsx | 15 + containers/layout/components/Header.tsx | 47 + content.json | 59 + contexts/AuthContext.tsx | 51 + contexts/ThemeContext.tsx | 42 + craft.txt | 0 docker-compose.yml | 17 + docs/blueprint.md | 24 + docs/filespec.md | 32 + features/workspace/WorkspaceContainer.tsx | 49 + features/workspace/WorkspaceView.tsx | 36 + features/workspace/components/StatusCard.tsx | 22 + features/workspace/components/StatusGrid.tsx | 31 + firebase-applet-config.json | 10 + firebase-blueprint.json | 73 ++ firestore.rules | 89 ++ hooks/useAuth.ts | 7 + hooks/useAuthSession.ts | 62 + hooks/useLocalStorage.ts | 31 + index.html | 13 + index.tsx | 22 + instructions.md | 43 + local_cookie.json | 4 + metadata.json | 7 + package.json | 37 + public/version.json | 1 + rules.md | 35 + services/apiService.ts | 160 +++ services/apiUtils.ts | 87 ++ services/appBuilder/componentService.ts | 122 ++ services/appBuilder/config.ts | 21 + services/appBuilder/contentService.ts | 1 + services/appBuilder/promptService.ts | 84 ++ services/appBuilder/rbacService.ts | 31 + services/appBuilderService.ts | 8 + services/geminiService.ts | 54 + services/llmService.ts | 89 ++ src/App.tsx | 1239 ++++++++++++++++++ src/components/AuthFlow.tsx | 1129 ++++++++++++++++ src/components/PrivacyPolicy.tsx | 90 ++ src/components/SubscriptionFlow.tsx | 202 +++ src/firebase.ts | 88 ++ src/index.css | 29 + src/main.tsx | 10 + src/styles/aura.css | 67 + src/styles/auth.css | 45 + src/styles/base.css | 49 + src/styles/components.css | 108 ++ src/styles/layout.css | 47 + src/styles/theme.css | 62 + src/styles/utilities.css | 28 + tsconfig.json | 26 + types.ts | 173 +++ vite.config.ts | 24 + wrangler.json | 4 + 69 files changed, 5922 insertions(+) create mode 100644 .gitea/workflows/feature_branch_autopr.yml create mode 100644 .gitignore create mode 100644 App.tsx create mode 100644 Dockerfile create mode 100644 FIXED_cloudflare_build.yml create mode 100644 README.md create mode 100644 References/api_storage.json create mode 100644 References/manager_api.json create mode 100644 components/Dashboard.tsx create mode 100644 components/Layout.tsx create mode 100644 components/SetupPrompt.tsx create mode 100644 components/icons.tsx create mode 100644 containers/MainContainer.tsx create mode 100644 containers/layout/MainLayout.tsx create mode 100644 containers/layout/components/Footer.tsx create mode 100644 containers/layout/components/Header.tsx create mode 100644 content.json create mode 100644 contexts/AuthContext.tsx create mode 100644 contexts/ThemeContext.tsx create mode 100644 craft.txt create mode 100644 docker-compose.yml create mode 100644 docs/blueprint.md create mode 100644 docs/filespec.md create mode 100644 features/workspace/WorkspaceContainer.tsx create mode 100644 features/workspace/WorkspaceView.tsx create mode 100644 features/workspace/components/StatusCard.tsx create mode 100644 features/workspace/components/StatusGrid.tsx create mode 100644 firebase-applet-config.json create mode 100644 firebase-blueprint.json create mode 100644 firestore.rules create mode 100644 hooks/useAuth.ts create mode 100644 hooks/useAuthSession.ts create mode 100644 hooks/useLocalStorage.ts create mode 100644 index.html create mode 100644 index.tsx create mode 100644 instructions.md create mode 100644 local_cookie.json create mode 100644 metadata.json create mode 100644 package.json create mode 100644 public/version.json create mode 100644 rules.md create mode 100644 services/apiService.ts create mode 100644 services/apiUtils.ts create mode 100644 services/appBuilder/componentService.ts create mode 100644 services/appBuilder/config.ts create mode 100644 services/appBuilder/contentService.ts create mode 100644 services/appBuilder/promptService.ts create mode 100644 services/appBuilder/rbacService.ts create mode 100644 services/appBuilderService.ts create mode 100644 services/geminiService.ts create mode 100644 services/llmService.ts create mode 100644 src/App.tsx create mode 100644 src/components/AuthFlow.tsx create mode 100644 src/components/PrivacyPolicy.tsx create mode 100644 src/components/SubscriptionFlow.tsx create mode 100644 src/firebase.ts create mode 100644 src/index.css create mode 100644 src/main.tsx create mode 100644 src/styles/aura.css create mode 100644 src/styles/auth.css create mode 100644 src/styles/base.css create mode 100644 src/styles/components.css create mode 100644 src/styles/layout.css create mode 100644 src/styles/theme.css create mode 100644 src/styles/utilities.css create mode 100644 tsconfig.json create mode 100644 types.ts create mode 100644 vite.config.ts create mode 100644 wrangler.json diff --git a/.gitea/workflows/feature_branch_autopr.yml b/.gitea/workflows/feature_branch_autopr.yml new file mode 100644 index 0000000..14bec33 --- /dev/null +++ b/.gitea/workflows/feature_branch_autopr.yml @@ -0,0 +1,128 @@ +name: Auto-PR feature -> playtest and merge +on: + push: + branches: + - "feature-**" +jobs: + pr_merge: + runs-on: ubuntu-latest + container: + image: node:20-bullseye + defaults: + run: + shell: bash + env: + # Change if your instance URL differs + GITEA_BASE_URL: https://git.code.svchub.com + PLAYTEST_BRANCH: playtest + + # Provide this as a Gitea Actions secret in the repo (Settings -> Actions -> Secrets) + # Must have rights to create PRs, merge PRs, and delete branches. + GITEA_TOKEN: ${{ vars.GIT_TOKEN }} + + # Repo + branch from the event context (GitHub-compatible in Gitea Actions) + REPO: ${{ github.repository }} # "owner/name" + BRANCH: ${{ github.ref_name }} # e.g. "feature/issue-123" + steps: + - name: Install prerequisites (rsync, jq, git) + run: | + set -eu + export DEBIAN_FRONTEND=noninteractive + if command -v apt-get >/dev/null 2>&1; then + apt-get update -y + apt-get install -y rsync jq git ca-certificates curl + elif command -v apk >/dev/null 2>&1; then + apk add --no-cache rsync jq git ca-certificates curl + elif command -v dnf >/dev/null 2>&1; then + dnf install -y rsync jq git ca-certificates curl + elif command -v yum >/dev/null 2>&1; then + yum install -y rsync jq git ca-certificates curl + else + echo "Unsupported base image"; exit 1 + fi + + - name: Create PR (if needed), merge with merge-commit, delete branch + run: | + set -euo pipefail + + : "${GITEA_TOKEN:?Missing secrets.GITEA_TOKEN}" + : "${REPO:?Missing repo context}" + : "${BRANCH:?Missing branch context}" + + OWNER="${REPO%%/*}" + NAME="${REPO##*/}" + + API="${GITEA_BASE_URL%/}/api/v1" + AUTH="Authorization: token ${GITEA_TOKEN}" + + FEATURE_BRANCH="${BRANCH}" + BASE_BRANCH="${PLAYTEST_BRANCH}" + + echo "[INFO] Repo: ${OWNER}/${NAME}" + echo "[INFO] Feature branch: ${FEATURE_BRANCH}" + echo "[INFO] Base branch: ${BASE_BRANCH}" + + # 1) Create PR from feature branch to playtest + echo "[INFO] Creating PR from ${FEATURE_BRANCH} → ${BASE_BRANCH}" + + PR_PAYLOAD="$(jq -n \ + --arg title "Auto-merge ${FEATURE_BRANCH} -> ${BASE_BRANCH}" \ + --arg head "${FEATURE_BRANCH}" \ + --arg base "${BASE_BRANCH}" \ + --arg body "Automated PR created on push to ${FEATURE_BRANCH}" \ + '{title:$title, head:$head, base:$base, body:$body}')" + + CREATE_URL="${API}/repos/${OWNER}/${NAME}/pulls" + CREATE_HTTP="$(curl -sS -o /tmp/pr_create.json -w "%{http_code}" \ + -H "Content-Type: application/json" -H "$AUTH" \ + -X POST -d "$PR_PAYLOAD" \ + "$CREATE_URL" || true)" + + if [ "$CREATE_HTTP" != "201" ]; then + echo "[ERROR] PR create failed (HTTP $CREATE_HTTP):" + cat /tmp/pr_create.json || true + exit 1 + fi + + PR_NUMBER="$(jq -r '.number' /tmp/pr_create.json)" + echo "[INFO] Created PR #${PR_NUMBER}" + + # 2) Merge PR using a merge commit ("Do": "merge") + # Branch deletion is handled separately in step 4 with safety checks + MERGE_PAYLOAD="$(jq -n \ + --arg do "merge" \ + --arg title "Merge ${FEATURE_BRANCH} into ${BASE_BRANCH}" \ + --arg message "Merged automatically from ${FEATURE_BRANCH}" \ + '{Do:$do, MergeTitleField:$title, MergeMessageField:$message}')" + + MERGE_URL="${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}/merge" + MERGE_HTTP="$(curl -sS -o /tmp/pr_merge.json -w "%{http_code}" \ + -H "Content-Type: application/json" -H "$AUTH" \ + -X POST -d "$MERGE_PAYLOAD" \ + "$MERGE_URL" || true)" + + if [ "$MERGE_HTTP" != "200" ] && [ "$MERGE_HTTP" != "201" ] && [ "$MERGE_HTTP" != "204" ]; then + echo "[ERROR] PR merge failed (HTTP $MERGE_HTTP):" + cat /tmp/pr_merge.json || true + exit 1 + fi + echo "[INFO] PR #${PR_NUMBER} merged." + + # 4) Ensure branch is deleted (in case delete-after-merge is disabled) + # Safety check: only delete if branch name starts with "feature-" or "bugfix-" + if [[ "${FEATURE_BRANCH}" == feature-* ]] || [[ "${FEATURE_BRANCH}" == bugfix-* ]]; then + echo "[INFO] Deleting temporary branch: ${FEATURE_BRANCH}" + DEL_URL="${API}/repos/${OWNER}/${NAME}/branches/${FEATURE_BRANCH}" + DEL_HTTP="$(curl -sS -o /tmp/branch_del.json -w "%{http_code}" \ + -H "$AUTH" -X DELETE \ + "$DEL_URL" || true)" + + if [ "$DEL_HTTP" = "204" ] || [ "$DEL_HTTP" = "200" ]; then + echo "[INFO] Branch ${FEATURE_BRANCH} deleted." + else + echo "[WARN] Branch delete returned HTTP $DEL_HTTP (may already be deleted or not permitted)." + cat /tmp/branch_del.json || true + fi + else + echo "[WARN] Branch ${FEATURE_BRANCH} does not match feature-* pattern. Skipping deletion for safety." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..a77d919 --- /dev/null +++ b/App.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import WorkspaceContainer from './features/workspace/WorkspaceContainer'; + +/** + * App is the root-level entry point. + * It delegates to specific feature containers to maintain a clean top-level structure. + */ +const App: React.FC = () => { + return ; +}; + +export default App; \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..628b6ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Use Node.js LTS version +FROM node:20-bookworm-slim + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy project files +COPY . . + +# Expose the port Vite runs on +EXPOSE 3000 + +# Start the development server +CMD ["npm", "run", "dev"] diff --git a/FIXED_cloudflare_build.yml b/FIXED_cloudflare_build.yml new file mode 100644 index 0000000..581f49f --- /dev/null +++ b/FIXED_cloudflare_build.yml @@ -0,0 +1,330 @@ +name: Cloudflare Worker AI Studio Build + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch name' + default: 'main' + clone_url: + description: 'Clone URL of repo' + required: true + original_url: + description: 'Original URL of repo' + required: false + use_original: + description: 'Use original repo URL' + default: 'false' +concurrency: + group: aistudio-build-group + cancel: false + +jobs: + deploy: + container: + image: node:20-bullseye + defaults: + run: + shell: bash + env: + BRANCH_NAME: ${{ github.event.inputs.branch || 'main' }} + CLONE_URL: ${{ github.event.inputs.clone_url }} + ORIGINAL_URL: ${{ github.event.inputs.original_url }} + USE_ORIGINAL: ${{ github.event.inputs.use_original || 'false' }} + GIT_USERNAME: ${{ vars.GIT_USERNAME }} + GIT_TOKEN: ${{ vars.GIT_TOKEN }} + CLOUDFLARE_API_TOKEN: ${{ github.event.inputs.branch == 'eteam_prod' && secrets.CF_API_TOKEN_ETEAM || secrets.CF_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ github.event.inputs.branch == 'eteam_prod' && vars.CF_ACCOUNT_ID_ETEAM || vars.CF_ACCOUNT_ID }} + + steps: + - name: Log input values + run: | + set -euo pipefail + echo "===== INPUT DETAILS =====" + echo "Branch: $BRANCH_NAME" + echo "Clone URL: $CLONE_URL" + echo "Original URL: ${ORIGINAL_URL:-}" + echo "Use Original: $USE_ORIGINAL" + echo "=========================" + + - name: Install prerequisites (rsync, jq, git) + run: | + set -euo pipefail + + if ! command -v git >/dev/null 2>&1; then + echo "[ERROR] git not found in image; cannot proceed without apt. Use an image that includes git." + exit 1 + fi + + if ! command -v jq >/dev/null 2>&1; then + echo "[INFO] Installing jq (static binary)..." + curl -fsSL -o /usr/local/bin/jq \ + https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 + chmod +x /usr/local/bin/jq + fi + + if ! command -v rsync >/dev/null 2>&1; then + echo "[WARN] rsync not found; continuing (workflow does not require rsync for core steps)." + fi + + jq --version + git --version + + - name: Clone repository + run: | + set -euo pipefail + TARGET_URL="$CLONE_URL" + if [ "$USE_ORIGINAL" = "true" ] && [ -n "${ORIGINAL_URL:-}" ]; then + TARGET_URL="$ORIGINAL_URL" + fi + AUTH_URL="$(echo "$TARGET_URL" | sed "s#https://#https://$GIT_USERNAME:$GIT_TOKEN@#")" + git clone --branch "$BRANCH_NAME" "$AUTH_URL" repo + cd repo + echo "[INFO] Repository cloned successfully." + git remote -v + + - name: Derive canonical repo name from origin and export + run: | + set -euo pipefail + ORIGIN_URL="$(git -C repo remote get-url origin)" + DERIVED_REPO_NAME="$(basename "${ORIGIN_URL%.git}")" + echo "[DEBUG] Derived repo name from origin: '${DERIVED_REPO_NAME}'" + echo "REPO_NAME=${DERIVED_REPO_NAME}" >> "$GITHUB_ENV" + + - name: Fetch metadata (slug + unique name) from AI Studio Manager API + run: | + set -euo pipefail + + ORIGIN_URL="$(git -C repo remote get-url origin)" + OWNER_REPO="$(echo "$ORIGIN_URL" | awk -F[/:] '{print $(NF-1)"/"$NF}' | sed 's/\.git$//')" + ENCODED_REPO_NAME="$(printf '%s' "$OWNER_REPO" | jq -s -R -r @uri)" + + if [[ "$BRANCH_NAME" == "main" ]]; then + MANAGER_API_URL="https://www.humanizeiq.ai" + elif [[ "$BRANCH_NAME" == "eteam_prod" ]]; then + MANAGER_API_URL="https://www.humanizeiq.ai" + elif [[ "$BRANCH_NAME" == "dev" ]]; then + MANAGER_API_URL="https://www.dev.humanizeiq.ai" + else + MANAGER_API_URL="https://www.playtest.humanizeiq.ai" + fi + + echo "[INFO] Using Manager API URL: $MANAGER_API_URL" + echo "[INFO] Querying AI Studio Manager API for repoName=$OWNER_REPO" + + RESPONSE="$(curl -sS -X GET \ + "${MANAGER_API_URL}/api/ai_studio_manager_api/app-builder/components/by-repo?repoName=${ENCODED_REPO_NAME}" \ + -H 'accept: application/json' \ + -H 'X-API-Key: system-key' || true)" + + echo "[DEBUG] API response: ${RESPONSE:-}" + + SLUG="$(echo "${RESPONSE:-}" | jq -r '.additional_info.slug' 2>/dev/null || echo "")" + UNIQUE_APP_CODE="$(echo "${RESPONSE:-}" | jq -r '.additional_info.unique_app_code' 2>/dev/null || echo "")" + + if [ -z "${SLUG:-}" ] || [ "${SLUG:-}" = "null" ]; then + echo "[WARN] slug missing; falling back to REPO_NAME#gais_." + BASE_NAME_VALUE="${REPO_NAME#gais_}" + else + BASE_NAME_VALUE="$SLUG" + fi + + if [ -z "${UNIQUE_APP_CODE:-}" ] || [ "${UNIQUE_APP_CODE:-}" = "null" ]; then + echo "[WARN] unique_app_code missing; falling back to REPO_NAME." + UNIQUE_NAME_VALUE="${REPO_NAME}" + else + UNIQUE_NAME_VALUE="${UNIQUE_APP_CODE}" + fi + + echo "BASE_NAME=${BASE_NAME_VALUE}" >> "$GITHUB_ENV" + echo "UNIQUE_NAME=${UNIQUE_NAME_VALUE}" >> "$GITHUB_ENV" + echo "MANAGER_API_URL=${MANAGER_API_URL}" >> "$GITHUB_ENV" + + echo "[INFO] Using BASE_NAME=${BASE_NAME_VALUE}" + echo "[INFO] Using UNIQUE_NAME=${UNIQUE_NAME_VALUE} for Worker base" + + - name: Install dependencies + working-directory: repo + run: | + set -euo pipefail + npm ci || npm install + + - name: Build AI Studio / Vite project + working-directory: repo + run: | + set -euo pipefail + BASE_NAME="${BASE_NAME:-${REPO_NAME#gais_}}" + BASE_PATH="/${BASE_NAME}/" + printf '[INFO] Using base path: %s\n' "$BASE_PATH" + npm run build --if-present -- --base "$BASE_PATH" + + - name: Copy JSON assets into dist + working-directory: repo + run: | + set -euo pipefail + cp -v ./metadata.json dist/ || echo "No JSON files found in ./config" + + - name: Install Wrangler CLI + run: | + set -euo pipefail + npm install -g wrangler + + - name: Deploy Cloudflare Worker + env: + CLOUDFLARE_API_TOKEN: ${{ env.BRANCH_NAME == 'eteam_prod' && secrets.CF_API_TOKEN_ETEAM || secrets.CF_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ env.BRANCH_NAME == 'eteam_prod' && vars.CF_ACCOUNT_ID_ETEAM || vars.CF_ACCOUNT_ID }} + working-directory: repo + run: | + set -euo pipefail + SAFE_BRANCH="${BRANCH_NAME//\//-}" + if [ "$SAFE_BRANCH" = "playtest" ]; then + SAFE_BRANCH="pt" + fi + + SLUG="${BASE_NAME:-${REPO_NAME#gais_}}" + WORKER_NAME="gais_${SLUG}_${SAFE_BRANCH}" + + MAX_LENGTH=54 + if [ ${#WORKER_NAME} -gt $MAX_LENGTH ]; then + echo "[WARN] Worker name '${WORKER_NAME}' is ${#WORKER_NAME} chars (max: ${MAX_LENGTH})" + HASH=$(echo -n "$WORKER_NAME" | md5sum | cut -c1-8) + MAX_SLUG_LEN=$((39 - ${#SAFE_BRANCH})) + TRUNCATED_SLUG="${SLUG:0:$MAX_SLUG_LEN}" + WORKER_NAME="gais_${TRUNCATED_SLUG}_${HASH}_${SAFE_BRANCH}" + echo "[INFO] Truncated to: ${WORKER_NAME} (${#WORKER_NAME} chars)" + fi + + echo "[INFO] Using Worker name: ${WORKER_NAME}" + + echo "[INFO] Writing wrangler.json..." + cat > wrangler.json <<'EOF' + { + "name": "PLACEHOLDER", + "compatibility_date": "1970-01-01", + "workers_dev": true + } + EOF + + jq \ + --arg name "$WORKER_NAME" \ + --arg date "$(date +%Y-%m-%d)" \ + '.name=$name | .compatibility_date=$date' \ + wrangler.json > wrangler.tmp && mv wrangler.tmp wrangler.json + + echo "[INFO] Verifying Wrangler auth..." + wrangler whoami + + echo "[INFO] Deploying..." + echo "[INFO] CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}" + wrangler deploy --assets ./dist + + - name: Update Route in Traefik Database + if: success() + run: | + set -euo pipefail + + SAFE_BRANCH="${BRANCH_NAME//\//-}" + if [ "$SAFE_BRANCH" = "playtest" ] || [ "$SAFE_BRANCH" = "pt" ]; then + ENVIRONMENT="playtest" + elif [ "$SAFE_BRANCH" = "dev" ]; then + ENVIRONMENT="nonprod" + elif [ "$SAFE_BRANCH" = "main" ]; then + ENVIRONMENT="prod" + elif [ "$SAFE_BRANCH" = "eteam_prod" ]; then + ENVIRONMENT="prod" + else + echo "[WARN] Unknown branch: $SAFE_BRANCH, defaulting to playtest" + ENVIRONMENT="playtest" + fi + + echo "[INFO] Updating route for environment: $ENVIRONMENT" + + APP_NAME="${BASE_NAME}" + + echo "[INFO] Calling route update API for app: $APP_NAME using $MANAGER_API_URL" + + ROUTE_RESPONSE="$(curl -sS -X POST \ + "${MANAGER_API_URL}/api/ai_studio_manager_api/app-builder/create-route" \ + -H 'Content-Type: application/json' \ + -H 'X-API-Key: system-key' \ + -d "{\"appName\":\"${APP_NAME}\",\"environment\":\"${ENVIRONMENT}\"}" || echo '{"status":"error","message":"API call failed"}')" + + echo "[INFO] Route update response: ${ROUTE_RESPONSE}" + + if echo "$ROUTE_RESPONSE" | jq -e '.status == "success"' > /dev/null 2>&1; then + ROUTE_URL="$(echo "$ROUTE_RESPONSE" | jq -r '.url')" + echo "[INFO] ✓ Route updated successfully: $ROUTE_URL" + else + ERROR_MSG="$(echo "$ROUTE_RESPONSE" | jq -r '.message // "Unknown error"')" + echo "[WARN] Route update may have failed: $ERROR_MSG" + echo "[WARN] Continuing workflow - route can be updated manually if needed" + fi + + - name: Create PR after successful build (NO MERGE) + if: success() + run: | + set -euo pipefail + cd repo + + ORIGIN_URL="$(git remote get-url origin)" + ORIGIN_URL="${ORIGIN_URL%.git}" + REPO_OWNER="$(echo "$ORIGIN_URL" | awk -F'/' '{print $(NF-1)}')" + REPO_NAME_ONLY="$(echo "$ORIGIN_URL" | awk -F'/' '{print $NF}')" + + GITEA_API="https://git.code.svchub.com/api/v1" + AUTH_HEADER="Authorization: token ${GIT_TOKEN}" + CURRENT_BRANCH="${BRANCH_NAME}" + + # Determine PR direction based on current branch + if [ "$CURRENT_BRANCH" = "playtest" ]; then + FROM_BRANCH="playtest" + TO_BRANCH="dev" + elif [ "$CURRENT_BRANCH" = "dev" ]; then + FROM_BRANCH="dev" + TO_BRANCH="main" + elif [ "$CURRENT_BRANCH" = "main" ]; then + if git ls-remote --exit-code --heads origin eteam_prod >/dev/null 2>&1; then + FROM_BRANCH="main" + TO_BRANCH="eteam_prod" + else + echo "[INFO] No PR rule for branch: $CURRENT_BRANCH (eteam_prod does not exist)" + exit 0 + fi + else + echo "[INFO] No PR rule for branch: $CURRENT_BRANCH" + exit 0 + fi + + echo "[INFO] Checking if PR already exists: ${FROM_BRANCH} → ${TO_BRANCH}" + + # Check if PR already exists + LIST_URL="${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME_ONLY}/pulls?state=open&base=${TO_BRANCH}&head=${REPO_OWNER}:${FROM_BRANCH}" + EXISTING_PR="$(curl -sS -H "$AUTH_HEADER" "$LIST_URL" | jq -r '.[0].number // empty')" + + if [ -n "$EXISTING_PR" ]; then + echo "[INFO] PR already exists: #${EXISTING_PR} (${FROM_BRANCH} → ${TO_BRANCH})" + echo "[INFO] Skipping PR creation to avoid duplicates" + exit 0 + fi + + echo "[INFO] Creating PR from ${FROM_BRANCH} → ${TO_BRANCH}" + PR_PAYLOAD=$(jq -n \ + --arg title "Auto PR: ${FROM_BRANCH} → ${TO_BRANCH}" \ + --arg head "$FROM_BRANCH" \ + --arg base "$TO_BRANCH" \ + --arg body "Automated PR created after successful build on ${FROM_BRANCH}" \ + '{title:$title, head:$head, base:$base, body:$body}') + + HTTP_CODE=$(curl -s -o /tmp/resp.json -w "%{http_code}" -X POST \ + -H "Content-Type: application/json" \ + -H "$AUTH_HEADER" \ + -d "$PR_PAYLOAD" \ + "${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME_ONLY}/pulls" || true) + + if [ "$HTTP_CODE" != "201" ]; then + echo "[WARN] PR creation failed or PR already exists (HTTP $HTTP_CODE). Response:" + cat /tmp/resp.json || true + else + PR_NUMBER="$(jq -r '.number' /tmp/resp.json)" + echo "[INFO] ✓ PR #${PR_NUMBER} created: ${FROM_BRANCH} → ${TO_BRANCH}" + fi diff --git a/README.md b/README.md new file mode 100644 index 0000000..21da801 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/9f4101c2-8730-451b-9134-3b46f26726c6 + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/References/api_storage.json b/References/api_storage.json new file mode 100644 index 0000000..48ad5ce --- /dev/null +++ b/References/api_storage.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"Auth SecureChat API","version":"1.0.0","description":"API for authentication with SecureChat using AnythingLLM"},"servers":[{"url":"/api/r2-explorer","description":"API Base Path"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authorization (Priority 1)"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Bearer token for authorization (Priority 2)"},"CookieAuth":{"type":"apiKey","in":"cookie","name":"auth","description":"Encrypted auth cookie for authorization (Priority 3). Provides user context for personalized operations."},"StudioCookieAuth":{"type":"apiKey","in":"header","name":"X-Studio-Cookie","description":"Encrypted auth cookie passed via X-Studio-Cookie header (Priority 3, alternative to Cookie). Provides user context for personalized operations."},"StudioCookieQueryAuth":{"type":"apiKey","in":"query","name":"X-Studio-Cookie","description":"Encrypted auth cookie passed via X-Studio-Cookie query parameter (Priority 3, alternative to Cookie/Header). Provides user context for personalized operations."}}},"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"paths":{"/health":{"get":{"summary":"Health check endpoint","description":"Returns the status of the API","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"API is running","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"message":{"type":"string","example":"Auth SecureChat API is running"},"version":{"type":"string","example":"1.0.0"}}}}}}}}},"/auth/validate":{"get":{"summary":"Validate authentication token (GET)","description":"Validates the auth token parameter via query parameter","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"auth","in":"query","description":"Authentication token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}},"post":{"summary":"Validate authentication token (POST)","description":"Validates the auth token parameter via request body","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}}},"/auth/decrypt-and-save":{"post":{"summary":"Decrypt auth token and save user data to database","description":"Decrypts the provided auth token and saves the user data to the D1 database","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token to decrypt"}}}}}},"responses":{"200":{"description":"User data successfully saved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"User data saved successfully"},"updated":{"type":"boolean","example":false},"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"userData":{"type":"object","properties":{"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"email":{"type":"string","example":"humanizeiq@eteaminc.com"},"firebase_uid":{"type":"string","example":"wFyjGE1j8wclXayUvPbkF4c15f92"},"firstname":{"type":"string","example":"Rajeev"},"lastname":{"type":"string","example":"Borborah"},"company_name":{"type":"string","nullable":true,"example":null}}}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid auth parameter"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to save user data"}}}}}}}}},"/upload":{"post":{"summary":"Upload file to R2 bucket","description":"Uploads a file to R2 bucket with path structure /data//Documents///. Requires authentication (API key, Bearer token, or auth cookie). Note: An auth cookie is required to identify the user (uid) for path construction. If using API key/Bearer token, you must also provide an auth cookie (via Cookie header, X-Studio-Cookie header, or X-Studio-Cookie query parameter).","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Encrypted auth cookie for user identification (alternative to Cookie/Header)","example":"base64-encoded-cookie-value"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload"},"metadata":{"type":"string","description":"JSON string containing metadata key/value pairs. Can include \"category\" field to specify file category. If category is not provided, it will be derived from file extension.","example":"{\"category\": \"Reports\", \"project\": \"Q4-2024\", \"author\": \"John Doe\"}"}}}}}},"responses":{"200":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"File uploaded successfully"},"path":{"type":"string","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"},"category":{"type":"string","example":"PDF"},"metadata":{"type":"object","example":{"category":"PDF","uploadDate":"2025-10-14T20:30:00.000Z","uid":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85","project":"Q4-2024","author":"John Doe"}}}}}}},"400":{"description":"Bad request - missing file or invalid metadata","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"No file provided or invalid file format"}}}}}},"401":{"description":"Unauthorized - missing or invalid auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"No auth cookie found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to upload file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/upload-generic":{"post":{"summary":"Generic system upload to R2 (no user auth cookie)","description":"Uploads a file to the R2 bucket using the same path logic as the regular /upload endpoint. Company, department, and user-specific behavior are derived from metadata instead of a user auth cookie. The request must be multipart/form-data with a file field and an optional metadata JSON string. Use the metadata fields \"company\"/\"company_name\"/\"companyName\" to control the company folder, \"department\"/\"Department\" for department, and \"user-specific\" (true/false) for user-specific vs shared paths.","tags":["Generic"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload"},"metadata":{"type":"string","description":"JSON string containing metadata key/value pairs. Should include company/department/user-specific to control path, and may include call_id, candidate_id, candidate_email, job_id, job_ref, etc.","example":"{\"uid\": \"system-generic\", \"company\": \"eTeam Inc\", \"Department\": \"Recruiting\", \"user-specific\": false, \"category\": \"AIInterviewReports\", \"call_id\": \"call_19913980616786_1763719874597\", \"candidate_id\": \"12345\", \"candidate_email\": \"candidate@example.com\", \"job_id\": \"26045639\", \"job_ref\": \"23-01265\"}"}}}}}},"responses":{"200":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"File uploaded successfully"},"path":{"type":"string","example":"eTeam Inc/departments/Recruiting/AIInterviewReports/2025-11-21/ai-summary-19745031817153-1761908089711.md"},"category":{"type":"string","example":"AIInterviewReports"},"metadata":{"type":"object","description":"Final metadata stored with the object in R2"},"user":{"type":"object","properties":{"uid":{"type":"string","example":"system-generic"},"email":{"type":"string","example":"candidate@example.com"}}},"savedIn":{"type":"string","example":"company"}}}}}},"400":{"description":"Bad request - missing file or invalid metadata JSON","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid metadata format. Must be valid JSON."},"details":{"type":"string"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to upload file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/files":{"get":{"summary":"List user files in tree format","description":"Retrieves all files for the authenticated user, optionally filtered by category. Returns files organized in a tree structure by category and date. Requires auth cookie (via Cookie header or X-Studio-Cookie header) for user identification.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Filter files by category (e.g., \"PDF\", \"Images\", \"Documents\"). If not provided, returns all files.","example":"PDF"},{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Encrypted auth cookie for user identification (alternative to Cookie/Header)","example":"base64-encoded-cookie-value"}],"responses":{"200":{"description":"Files retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"category":{"type":"string","example":"PDF"},"totalFiles":{"type":"integer","example":5},"tree":{"type":"object","description":"Tree structure organized by category and date","example":{"PDF":{"2025-10-14":[{"fileId":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTE0L2RvY3VtZW50LnBkZg==","name":"document.pdf","path":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf","size":1048576,"uploaded":"2025-10-14T20:30:00.000Z","etag":"abc123def456","httpMetadata":{"contentType":"application/pdf"},"customMetadata":{"category":"PDF","uploadDate":"2025-10-14T20:30:00.000Z","uid":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85","fileName":"document.pdf","fileSize":"1048576","uploadTimestamp":"1729028400000"}}],"2025-10-13":[{"fileId":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTEzL3JlcG9ydC5wZGY=","name":"report.pdf","path":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-13/report.pdf","size":2097152,"uploaded":"2025-10-13T15:20:00.000Z","etag":"xyz789abc012","httpMetadata":{"contentType":"application/pdf"},"customMetadata":{"category":"PDF","uploadDate":"2025-10-13T15:20:00.000Z","uid":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85","fileName":"report.pdf","fileSize":"2097152","uploadTimestamp":"1728842400000"}}]}}}}}}}},"400":{"description":"Bad request - missing auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Auth cookie required"},"message":{"type":"string","example":"User identification required via auth cookie"}}}}}},"401":{"description":"Unauthorized - invalid or expired auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid or expired auth cookie"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to list files"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/files-by-department":{"get":{"summary":"List files by department","description":"Retrieves all files for a specific department, organized by category. Supports optional category filtering.","tags":["Files"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"department","in":"query","required":true,"schema":{"type":"string"},"description":"Department name to retrieve files from"},{"name":"category","in":"query","required":false,"schema":{"type":"string"},"description":"Optional category filter (e.g., \"PDF\", \"Documents\", \"Images\")"},{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Base64 encoded auth cookie (alternative to header or cookie)"}],"responses":{"200":{"description":"Successfully retrieved department files","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"company":{"type":"string","example":"acme-corp"},"department":{"type":"string","example":"engineering"},"category":{"type":"string","example":"all"},"totalFiles":{"type":"integer","example":15},"tree":{"type":"object","description":"Files organized by category","additionalProperties":{"type":"array","items":{"type":"object","properties":{"fileId":{"type":"string","description":"Base64 encoded file identifier"},"name":{"type":"string","example":"report.pdf"},"path":{"type":"string","example":"acme-corp/engineering/PDF/report.pdf"},"size":{"type":"integer","example":1024000},"uploaded":{"type":"string","format":"date-time"},"etag":{"type":"string"},"httpMetadata":{"type":"object"},"customMetadata":{"type":"object"}}}},"example":{"PDF":[{"fileId":"YWNtZS1jb3JwL2VuZ2luZWVyaW5nL1BERi9yZXBvcnQucGRm","name":"report.pdf","path":"acme-corp/engineering/PDF/report.pdf","size":1024000,"uploaded":"2024-01-15T10:30:00.000Z"}],"Documents":[{"fileId":"YWNtZS1jb3JwL2VuZ2luZWVyaW5nL0RvY3VtZW50cy9zcGVjLmRvY3g=","name":"spec.docx","path":"acme-corp/engineering/Documents/spec.docx","size":512000,"uploaded":"2024-01-16T14:20:00.000Z"}]}}}}}}},"400":{"description":"Bad request - missing department parameter or not a company user","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"department parameter is required"}}}}}},"401":{"description":"Unauthorized - invalid or missing auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid or expired auth cookie"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to list department files"},"details":{"type":"string"}}}}}}}}},"/files-by-metadata":{"post":{"summary":"Get files by metadata","description":"Retrieves files based on metadata filters using the same folder structure logic as upload. Supports filtering by department, category, and user-specific flag. Requires auth cookie for user identification.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]},{"StudioCookieQueryAuth":[]}],"parameters":[{"name":"X-Studio-Cookie","in":"query","required":false,"schema":{"type":"string"},"description":"Encrypted auth cookie for user identification (alternative to Cookie/Header)","example":"base64-encoded-cookie-value"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"metadata":{"type":"object","properties":{"department":{"type":"string","description":"Department name (defaults to \"Everyone\" if not provided)","example":"Recruiting"},"category":{"type":"string","description":"File category (e.g., \"Resumes\", \"PDF\", \"Images\")","example":"Resumes"},"user-specific":{"type":"boolean","description":"Whether to retrieve user-specific files (defaults to true). Files without department are always user-specific.","example":true}}}},"example":{"metadata":{"department":"Recruiting","category":"Resumes","user-specific":true}}}}}},"responses":{"200":{"description":"Files retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"totalFiles":{"type":"integer","example":3},"filters":{"type":"object","properties":{"company":{"type":"string","example":"HumanizeIQ"},"department":{"type":"string","example":"Recruiting"},"category":{"type":"string","example":"Resumes"},"userSpecific":{"type":"boolean","example":true},"uid":{"type":"string","example":"7939cfe1-c6b4-4cfa-8691-ada77f011553"}}},"files":{"type":"array","items":{"type":"object","properties":{"fileId":{"type":"string","description":"Base64 encoded file path","example":"SHVtYW5pemVJUS9kZXBhcnRtZW50cy9SZWNydWl0aW5nL1Jlc3VtZXMvNzkzOWNmZTEtYzZiNC00Y2ZhLTg2OTEtYWRhNzdmMDExNTUzLzIwMjUtMTAtMjMvcmVzdW1lLnBkZg=="},"name":{"type":"string","example":"resume.pdf"},"path":{"type":"string","example":"HumanizeIQ/departments/Recruiting/Resumes/7939cfe1-c6b4-4cfa-8691-ada77f011553/2025-10-23/resume.pdf"},"size":{"type":"integer","example":524288},"uploaded":{"type":"string","format":"date-time","example":"2025-10-23T15:30:00.000Z"},"etag":{"type":"string","example":"abc123def456"},"httpMetadata":{"type":"object","properties":{"contentType":{"type":"string","example":"application/pdf"}}},"customMetadata":{"type":"object","properties":{"category":{"type":"string","example":"Resumes"},"uploadDate":{"type":"string","example":"2025-10-23T15:30:00.000Z"},"uid":{"type":"string","example":"7939cfe1-c6b4-4cfa-8691-ada77f011553"},"fileName":{"type":"string","example":"resume.pdf"},"department":{"type":"string","example":"Recruiting"}}}}}}}}}}},"400":{"description":"Bad request - missing auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Auth cookie required"},"message":{"type":"string","example":"User identification required via auth cookie"}}}}}},"401":{"description":"Unauthorized - invalid or expired auth cookie","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid or expired auth cookie"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to get files by metadata"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/download-file":{"get":{"summary":"Get file contents by fileId or path","description":"Retrieves a file from R2 storage by its encoded fileId (recommended) or full path. Returns the file contents with appropriate headers including metadata. The fileId is provided in the /files endpoint response.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"fileId","in":"query","required":false,"schema":{"type":"string"},"description":"Base64 encoded file ID (obtained from /files endpoint)","example":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTE0L2RvY3VtZW50LnBkZg=="},{"name":"path","in":"query","required":false,"schema":{"type":"string"},"description":"Full path to the file in R2 storage (alternative to fileId)","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"}],"responses":{"200":{"description":"File retrieved successfully","headers":{"Content-Type":{"schema":{"type":"string"},"description":"MIME type of the file","example":"application/pdf"},"Content-Length":{"schema":{"type":"string"},"description":"Size of the file in bytes","example":"1048576"},"ETag":{"schema":{"type":"string"},"description":"Entity tag for the file","example":"abc123def456"},"Last-Modified":{"schema":{"type":"string"},"description":"ISO 8601 timestamp of when the file was uploaded","example":"2025-10-14T20:30:00.000Z"},"X-File-Path":{"schema":{"type":"string"},"description":"Full path of the file in R2","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"},"X-Custom-Metadata":{"schema":{"type":"string"},"description":"JSON string of custom metadata associated with the file","example":"{\"category\":\"PDF\",\"uploadDate\":\"2025-10-14T20:30:00.000Z\",\"uid\":\"426bcea5-adb9-4580-a8ca-3a40fdb0ef85\"}"}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Bad request - missing fileId/path parameter or invalid fileId","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Either fileId or path parameter is required"}}}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to get file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/update-file":{"put":{"summary":"Update an existing file","description":"Updates an existing file in R2 storage by replacing its content. The file is identified by fileId (recommended) or path. Optionally updates metadata while preserving existing metadata.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"fileId","in":"query","required":false,"schema":{"type":"string"},"description":"Base64 encoded file ID (obtained from /files endpoint)","example":"ZGF0YS80MjZiY2VhNS1hZGI5LTQ1ODAtYThjYS0zYTQwZmRiMGVmODUvRG9jdW1lbnRzL1BERi8yMDI1LTEwLTE0L2RvY3VtZW50LnBkZg=="},{"name":"path","in":"query","required":false,"schema":{"type":"string"},"description":"Full path to the file in R2 storage (alternative to fileId)","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"The new file content to replace the existing file"},"metadata":{"type":"string","description":"Optional JSON string of metadata to update (merged with existing metadata)","example":"{\"version\":\"2.0\",\"updatedBy\":\"user@example.com\"}"}}}}}},"responses":{"200":{"description":"File updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"File updated successfully"},"path":{"type":"string","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"},"size":{"type":"integer","example":2097152},"contentType":{"type":"string","example":"application/pdf"},"metadata":{"type":"object","example":{"category":"PDF","version":"2.0","updatedBy":"user@example.com","lastModified":"2025-10-15T20:30:00.000Z"}}}}}}},"400":{"description":"Bad request - missing parameters or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File is required"}}}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File not found"},"path":{"type":"string","example":"data/426bcea5-adb9-4580-a8ca-3a40fdb0ef85/Documents/PDF/2025-10-14/document.pdf"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to update file"},"details":{"type":"string","example":"Error message details"}}}}}}}}},"/delete-file":{"delete":{"tags":["Files"],"summary":"Delete a file","description":"Delete a file from R2 storage by fileId or path. Requires authentication.","parameters":[{"name":"fileId","in":"query","schema":{"type":"string"},"description":"The encoded file ID (base64 of the R2 key)"},{"name":"path","in":"query","schema":{"type":"string"},"description":"The direct path to the file in R2 (alternative to fileId)"}],"responses":{"200":{"description":"File deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"File deleted successfully"},"path":{"type":"string","example":"consumer/data/uid/Documents/test.txt"}}}}}},"400":{"description":"Bad Request - Missing or invalid parameters","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"fileId parameter is required"}}}}}},"401":{"description":"Unauthorized - Invalid or missing auth cookie"},"404":{"description":"Not Found - File does not exist","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"File not found"},"path":{"type":"string"}}}}}},"500":{"description":"Internal Server Error"}}}}}} \ No newline at end of file diff --git a/References/manager_api.json b/References/manager_api.json new file mode 100644 index 0000000..4bb54ff --- /dev/null +++ b/References/manager_api.json @@ -0,0 +1 @@ +{"openapi":"3.0.0","info":{"title":"AI Studio Manager API","version":"1.0.0","description":"API for AI Studio Manager including authentication and app builder functionality"},"servers":[{"url":"/api/ai_studio_manager_api","description":"Auth API Base Path"},{"url":"/api","description":"App Builder API Base Path"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authorization"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Bearer token for authorization"},"CookieAuth":{"type":"apiKey","in":"cookie","name":"auth","description":"Authentication cookie (encrypted)"},"StudioCookieAuth":{"type":"apiKey","in":"query","name":"X-Studio-Cookie","description":"Base64-encoded encrypted cookie for Google AI Studio"}}},"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"tags":[{"name":"Authentication","description":"Authentication and user management endpoints"},{"name":"App Builder","description":"App builder endpoints for managing applications and repositories"},{"name":"CMS","description":"Content Management System endpoints for managing companies, projects, and components"},{"name":"Work Management","description":"Work management endpoints for managing tasks"},{"name":"RBAC","description":"Role-Based Access Control endpoints for managing roles, permissions, and user assignments"}],"paths":{"/health":{"get":{"summary":"Health check endpoint","description":"Returns the status of the API","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"API is running","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"message":{"type":"string","example":"Auth SecureChat API is running"},"version":{"type":"string","example":"1.0.0"}}}}}}}}},"/auth/validate":{"get":{"summary":"Validate authentication token (GET)","description":"Validates the auth token parameter via query parameter","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"auth","in":"query","description":"Authentication token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}},"post":{"summary":"Validate authentication token (POST)","description":"Validates the auth token parameter via request body","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"firstname":{"type":"string","example":"John"},"lastname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john.doe@example.com"}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication failed"}}}}}}}}},"/auth/decrypt-and-save":{"post":{"summary":"Decrypt auth token and save user data to database","description":"Decrypts the provided auth token and saves the user data to the D1 database","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"string","description":"Authentication token to decrypt"}}}}}},"responses":{"200":{"description":"User data successfully saved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"User data saved successfully"},"updated":{"type":"boolean","example":false},"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"userData":{"type":"object","properties":{"uid":{"type":"string","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"email":{"type":"string","example":"humanizeiq@eteaminc.com"},"firebase_uid":{"type":"string","example":"wFyjGE1j8wclXayUvPbkF4c15f92"},"firstname":{"type":"string","example":"Rajeev"},"lastname":{"type":"string","example":"Borborah"},"company_name":{"type":"string","nullable":true,"example":null}}}}}}}},"401":{"description":"Authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid auth parameter"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Failed to save user data"}}}}}}}}},"/app-builder/apps":{"get":{"summary":"Get all apps","description":"Retrieves all apps from the database (no user filter)","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"responses":{"200":{"description":"List of apps","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"my-app"},"createdBy":{"type":"string","example":"user@example.com"},"createdAt":{"type":"string","format":"date-time","example":"2025-01-01T12:00:00Z"},"userId":{"type":"string","example":"user123"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Unauthorized"}}}}}}}},"post":{"summary":"Create a new app","description":"Creates a new app entry in the database. App names must be unique across all users.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app"}}}}}},"responses":{"201":{"description":"App created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"App 'my-app' created successfully."},"appId":{"type":"integer","example":1}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Authentication required. Please provide valid credentials."}}}}}},"409":{"description":"App already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"An app with this name already exists."}}}}}}}}},"/app-builder/add-github-webhook":{"post":{"summary":"Add or update webhook in GitHub repository","description":"Adds a webhook to the specified GitHub repository. If the webhook already exists, it updates the webhook to match current specifications. The webhook URL is https://services.nonprod.svchub.com/git_webhooks/github_cloner with content type application/json, SSL verification enabled, webhook secret from WEBHOOK_SECRET environment variable, and sends all events.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"}}}}}},"responses":{"200":{"description":"Webhook updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook updated successfully"},"alreadyExists":{"type":"boolean","example":true},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123456},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/github_cloner"},"events":{"type":"array","items":{"type":"string"},"example":["*"]},"active":{"type":"boolean","example":true}}}}}}}},"201":{"description":"Webhook created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook created successfully"},"alreadyExists":{"type":"boolean","example":false},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123456},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/github_cloner"},"events":{"type":"array","items":{"type":"string"},"example":["*"]},"active":{"type":"boolean","example":true}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"401":{"description":"Authentication or credentials error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"GitHub credentials are invalid. Please update your credentials."}}}}}},"404":{"description":"Repository or credentials not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"GitHub repository not found. Please ensure the repository exists."}}}}}}}}},"/app-builder/github-credentials":{"get":{"summary":"Get GitHub credentials","description":"Retrieves the saved GitHub username (token is never returned)","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"responses":{"200":{"description":"GitHub username","content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","example":"jane-doe"}}}}}},"404":{"description":"Credentials not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Credentials not found for this user."}}}}}}}},"post":{"summary":"Save GitHub credentials","description":"Saves or updates GitHub username and personal access token","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","token"],"properties":{"username":{"type":"string","example":"jane-doe"},"token":{"type":"string","example":"ghp_xxxxxxxxxxxx"}}}}}},"responses":{"200":{"description":"Credentials saved","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Credentials saved successfully."}}}}}},"400":{"description":"Invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Invalid username or token provided."}}}}}}}}},"/app-builder/check-github-repo":{"post":{"summary":"Check GitHub repository existence","description":"Checks if a repository exists using stored GitHub credentials","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app"}}}}}},"responses":{"200":{"description":"Repository check result","content":{"application/json":{"schema":{"type":"object","properties":{"exists":{"type":"boolean","example":true}}}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"GitHub credentials are invalid. Please update your credentials."}}}}}},"404":{"description":"No credentials found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"No GitHub credentials found. Please save your credentials first."}}}}}}}}},"/app-builder/check-gitea-repo":{"get":{"summary":"Check Gitea repository existence","description":"Checks if a repository exists in HumanizeIQ Gitea","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"appName","in":"query","required":true,"schema":{"type":"string"},"example":"my-cool-app"}],"responses":{"200":{"description":"Repository check result","content":{"application/json":{"schema":{"type":"object","properties":{"exists":{"type":"boolean","example":false}}}}}}}}},"/app-builder/add-gitea-webhook":{"post":{"summary":"Add or update webhook in Gitea repository","description":"Adds a webhook to the specified Gitea repository. If the webhook already exists, it updates the webhook to match current specifications. The webhook URL is https://services.nonprod.svchub.com/git_webhooks/gitea with content type application/json, triggers on push and pull request closed events for all branches, and includes Bearer system-key authorization header.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"}}}}}},"responses":{"200":{"description":"Webhook updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook updated successfully"},"alreadyExists":{"type":"boolean","example":true},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/gitea"},"events":{"type":"array","items":{"type":"string"},"example":["push","pull_request_closed"]},"branch_filter":{"type":"string","example":"*"},"active":{"type":"boolean","example":true}}}}}}}},"201":{"description":"Webhook created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Webhook created successfully"},"alreadyExists":{"type":"boolean","example":false},"webhook":{"type":"object","properties":{"id":{"type":"number","example":123},"url":{"type":"string","example":"https://services.nonprod.svchub.com/git_webhooks/gitea"},"events":{"type":"array","items":{"type":"string"},"example":["push","pull_request_closed"]},"branch_filter":{"type":"string","example":"*"},"active":{"type":"boolean","example":true}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"404":{"description":"Repository not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Gitea repository not found. Please ensure the repository exists."}}}}}}}}},"/app-builder/gitea-pull-requests":{"get":{"tags":["App Builder"],"summary":"Get open pull requests from Gitea","description":"Retrieves all open pull requests for a component's Gitea repository","security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"componentId","in":"query","required":true,"description":"Component ID to fetch pull requests for","schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Pull requests retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"pullRequests":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":12345},"number":{"type":"integer","example":42},"title":{"type":"string","example":"Merge playtest into dev"},"state":{"type":"string","example":"open"},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/repo/pulls/42"},"sourceBranch":{"type":"string","example":"playtest"},"targetBranch":{"type":"string","example":"dev"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"author":{"type":"string","example":"username"},"mergeable":{"type":"boolean","example":true},"merged":{"type":"boolean","example":false},"draft":{"type":"boolean","example":false},"envName":{"type":"string","example":"NonProd","nullable":true},"environment":{"type":"string","example":"dev","nullable":true}}}},"count":{"type":"integer","example":1}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required."}}}}}},"401":{"description":"Unauthorized - authentication required","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Authentication required. Please provide valid credentials."}}}}}},"404":{"description":"Component or repository not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"An unexpected error occurred while fetching pull requests. Please try again."}}}}}}}}},"/app-builder/gitea-pr-status":{"get":{"summary":"Get Gitea pull request status","description":"Gets the status of a specific pull request by ID, including merge status, state, conflict status, and branch information.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"parameters":[{"name":"appName","in":"query","required":true,"schema":{"type":"string"},"example":"my-app"},{"name":"prId","in":"query","required":true,"schema":{"type":"integer"},"example":1}],"responses":{"200":{"description":"Pull request status retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"pr":{"type":"object","properties":{"id":{"type":"integer","example":1},"title":{"type":"string","example":"Feature: Add new functionality"},"state":{"type":"string","example":"open"},"merged":{"type":"boolean","example":false},"mergeable":{"type":"boolean","example":true},"hasConflicts":{"type":"boolean","example":false,"description":"True when mergeable is false, indicating merge conflicts"},"base":{"type":"string","example":"main"},"head":{"type":"string","example":"dev"},"created_at":{"type":"string","example":"2025-01-01T12:00:00Z"},"updated_at":{"type":"string","example":"2025-01-02T12:00:00Z"},"merged_at":{"type":"string","nullable":true,"example":null},"html_url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app/pulls/1"}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName query parameter is required."}}}}}},"404":{"description":"Pull request not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request not found. Please check the PR ID and app name."}}}}}}}}},"/app-builder/merge-gitea-pr":{"post":{"summary":"Merge and close Gitea pull request","description":"Initiates an asynchronous merge of a pull request in Gitea using merge commit strategy with force merge enabled. Returns immediately with status 202. Automatically creates route entries: playtest→dev creates dev route in ROUTES_DB (services.nonprod.svchub.com/{app_name}), dev→main creates prod route in ROUTES_DB_PROD (services.prod.svchub.com/{app_name}). Use the PR status endpoint to check merge completion.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","prId"],"properties":{"appName":{"type":"string","example":"my-app"},"prId":{"type":"integer","example":1,"description":"Pull request ID/number"}}}}}},"responses":{"202":{"description":"Pull request merge initiated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Pull request #1 merge initiated. Use the PR status endpoint to check merge status."},"prId":{"type":"integer","example":1},"initiated":{"type":"boolean","example":true}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"404":{"description":"Pull request not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request not found. Please check the PR ID and app name."}}}}}},"405":{"description":"Pull request cannot be merged","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Cannot merge PR: PR is not mergeable"}}}}}},"409":{"description":"Pull request already merged","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request has already been merged."}}}}}}}}},"/app-builder/redeploy-app":{"post":{"summary":"Redeploy an app","description":"Triggers a redeploy of an app on a specific branch by calling {GITEA_WEBHOOK_BASE_URL}/redeploy with Authorization Bearer token and X-GitHub-Event header (base URL configurable via GITEA_WEBHOOK_BASE_URL, auth token via GITEA_WEBHOOK_AUTH_TOKEN)","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","branch"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app to redeploy"},"branch":{"type":"string","enum":["main","dev","playtest"],"example":"main","description":"Branch to deploy (main, dev, or playtest)"}}}}}},"responses":{"200":{"description":"Redeploy triggered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Redeploy triggered successfully for gais_my_app on branch main"},"repositoryName":{"type":"string","example":"gais_my_app"},"branch":{"type":"string","example":"main"},"cloneUrl":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app"}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"502":{"description":"Redeploy service error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Redeploy service error: 500 - Internal Server Error"}}}}}}}}},"/app-builder/force-deploy":{"post":{"summary":"Force deploy an app","description":"Triggers a force deploy workflow for an app via webhook service. This bypasses normal deployment checks and forces a deployment for a specific PR.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","prId"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"},"prId":{"type":"integer","example":1,"description":"Pull request ID/number"}}}}}},"responses":{"200":{"description":"Force deploy workflow triggered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Force deploy workflow triggered successfully for gais_my_app"},"appName":{"type":"string","example":"my-app"},"prId":{"type":"integer","example":1},"repository":{"type":"string","example":"HumanizeIQ/gais_my_app"},"httpStatus":{"type":"integer","example":204},"giteaResponse":{"type":"string","nullable":true}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"appName is required."}}}}}},"403":{"description":"Unauthorized","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Unauthorized: Invalid webhook authentication token"}}}}}},"404":{"description":"Webhook endpoint not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Force deploy webhook endpoint not found"}}}}}}}}},"/app-builder/create-defect-issue":{"post":{"summary":"Create a defect issue","description":"Creates a defect issue in the Gitea repository for an app with optional file attachments. Attachments are uploaded first, then linked in the issue body.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName","title"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app/repository"},"title":{"type":"string","example":"Bug: Login button not working","description":"Issue title"},"description":{"type":"string","example":"When clicking the login button, nothing happens. Expected behavior: should redirect to dashboard.","description":"Issue description/body (optional)"},"attachments":{"type":"array","description":"Optional array of file attachments","items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"screenshot.png","description":"Filename with extension"},"type":{"type":"string","example":"image/png","description":"MIME type (optional, defaults to application/octet-stream)"},"data":{"type":"string","example":"iVBORw0KGgoAAAANSUhEUgAAAAUA...","description":"Base64 encoded file content (with or without data URI prefix)"}}}}}}}}},"responses":{"201":{"description":"Defect issue created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Defect issue #5 created successfully"},"issue":{"type":"object","properties":{"id":{"type":"integer","example":123},"number":{"type":"integer","example":5},"title":{"type":"string","example":"Bug: Login button not working"},"body":{"type":"string","example":"Description...\n\n### Attachments\n- [screenshot.png](https://...)"},"state":{"type":"string","example":"open"},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app/issues/5"},"created_at":{"type":"string","example":"2025-01-01T12:00:00Z"}}},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","example":"screenshot.png"},"url":{"type":"string","example":"https://git.code.svchub.com/..."}}}}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"title is required."}}}}}},"403":{"description":"Access forbidden","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Access forbidden. Please check token permissions."}}}}}},"404":{"description":"Repository not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Repository not found. Please ensure the repository exists."}}}}}}}}},"/app-builder/create-gitea-repo":{"post":{"summary":"Create Gitea repository","description":"Creates a new repository in HumanizeIQ Gitea instance with three branches: main, dev, and playtest. Also saves the app to the database. App names must be unique - returns 409 if an app with the same name already exists.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app"}}}}}},"responses":{"201":{"description":"Repository created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Repository 'gais_my_app' created successfully with branches: main, dev, playtest."},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_my_app"},"branches":{"type":"array","items":{"type":"object","properties":{"branch":{"type":"string","example":"dev"},"created":{"type":"boolean","example":true},"alreadyExists":{"type":"boolean","example":false}}}}}}}}},"409":{"description":"App name already exists or repository already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"A repository with this name already exists. Missing branches have been created."},"branches":{"type":"array","items":{"type":"object","properties":{"branch":{"type":"string","example":"dev"},"created":{"type":"boolean","example":false},"alreadyExists":{"type":"boolean","example":true}}}}}}}}}}}},"/app-builder/create-route":{"post":{"summary":"Create or update route entry","description":"Creates a new route entry in the routes database for playtest, nonprod, or prod environments. If a route already exists for the app, it updates the existing route. All URLs are configurable via BASE_URL and WORKER_DOMAIN environment variables.","tags":["App Builder"],"security":[{"ApiKeyAuth":[]},{"CookieAuth":[]},{"StudioCookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appName"],"properties":{"appName":{"type":"string","example":"my-app","description":"Name of the app"},"environment":{"type":"string","enum":["playtest","nonprod","prod"],"default":"playtest","example":"playtest","description":"Environment to create route for (playtest, nonprod, or prod)"}}}}}},"responses":{"200":{"description":"Route updated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Route for 'my-app' in nonprod environment updated successfully."},"url":{"type":"string","example":"https://services.nonprod.svchub.com/my_app"},"environment":{"type":"string","example":"nonprod"},"updated":{"type":"boolean","example":true}}}}}},"201":{"description":"Route created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Route for 'my-app' in playtest environment created successfully."},"url":{"type":"string","example":"https://services.nonprod.svchub.com/playtest/my_app"},"environment":{"type":"string","example":"playtest"},"updated":{"type":"boolean","example":false}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Invalid environment. Must be one of: playtest, nonprod, prod"}}}}}}}}},"/app-builder/organizations":{"get":{"tags":["App Builder"],"summary":"Get all organizations","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"List of organizations","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create a new organization","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"201":{"description":"Organization created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"organization":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}},"409":{"description":"Conflict - organization already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update an existing organization","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","name"],"properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"Updated Org"},"description":{"type":"string","example":"Updated Description"}}}}}},"responses":{"200":{"description":"Organization updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Organization updated successfully."},"organization":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"Updated Org"},"description":{"type":"string","example":"Updated Description"},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Organization ID and name are required."}}}}}},"404":{"description":"Organization not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Organization not found"}}}}}},"409":{"description":"Conflict - organization name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Organization with this name already exists"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update organization"}}}}}}}}},"/app-builder/projects":{"get":{"tags":["App Builder"],"summary":"Get all projects","description":"Retrieve all projects, optionally filtered by organization","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"organizationId","in":"query","description":"Filter projects by organization ID","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"organizationId":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - invalid organizationId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create a new project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","organizationId"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"organizationId":{"type":"integer"}}}}}},"responses":{"201":{"description":"Project created","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"project":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"},"organizationId":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}},"404":{"description":"Organization not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}},"409":{"description":"Conflict - project already exists in organization","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update a project","description":"Updates an existing project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":103,"description":"Project ID"},"name":{"type":"string","example":"Updated Project Name","description":"Project name (optional)"},"description":{"type":"string","example":"Updated project description","description":"Project description (optional)"}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Project updated successfully."},"project":{"type":"object","properties":{"id":{"type":"integer","example":103},"name":{"type":"string","example":"Updated Project"},"description":{"type":"string","example":"Updated Description"},"organization_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project ID is required."}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update project"}}}}}}}}},"/cms/companies":{"get":{"tags":["CMS"],"summary":"Get all companies","description":"Retrieve all companies in the CMS","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"List of companies","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"eTeam"},"slug":{"type":"string","example":"eteam"},"description":{"type":"string","example":"eTeam Inc."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch companies"}}}}}}}}},"/cms/companies/{companySlug}":{"get":{"tags":["CMS"],"summary":"Get company by slug","description":"Retrieve a specific company by its slug","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}}],"responses":{"200":{"description":"Company details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"eTeam"},"slug":{"type":"string","example":"eteam"},"description":{"type":"string","example":"eTeam Inc."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Company not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch company"}}}}}}}}},"/cms/companies/{companySlug}/projects":{"get":{"tags":["CMS"],"summary":"Get projects for a company","description":"Retrieve all projects for a specific company","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}}],"responses":{"200":{"description":"List of projects","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Corporate Website"},"slug":{"type":"string","example":"corporate-web"},"description":{"type":"string","example":"Main corporate website"},"company_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Company not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch projects"}}}}}}}}},"/cms/companies/{companySlug}/projects/{projectSlug}":{"get":{"tags":["CMS"],"summary":"Get project by slug","description":"Retrieve a specific project by its slug","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}},{"name":"projectSlug","in":"path","required":true,"description":"Project slug identifier","schema":{"type":"string","example":"corporate-web"}}],"responses":{"200":{"description":"Project details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Corporate Website"},"slug":{"type":"string","example":"corporate-web"},"description":{"type":"string","example":"Main corporate website"},"company_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch project"}}}}}}}}},"/cms/companies/{companySlug}/projects/{projectSlug}/components":{"get":{"tags":["CMS"],"summary":"Get components for a project","description":"Retrieve all components for a specific project (metadata only, no fields)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}},{"name":"projectSlug","in":"path","required":true,"description":"Project slug identifier","schema":{"type":"string","example":"corporate-web"}}],"responses":{"200":{"description":"List of components","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Home Page"},"slug":{"type":"string","example":"homePage"},"project_id":{"type":"integer","example":1},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch components"}}}}}}}}},"/cms/companies/{companySlug}/projects/{projectSlug}/components/{componentSlug}":{"get":{"tags":["CMS"],"summary":"Get component content","description":"Retrieve a specific component with its fields and content","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"companySlug","in":"path","required":true,"description":"Company slug identifier","schema":{"type":"string","example":"eteam"}},{"name":"projectSlug","in":"path","required":true,"description":"Project slug identifier","schema":{"type":"string","example":"corporate-web"}},{"name":"componentSlug","in":"path","required":true,"description":"Component slug identifier","schema":{"type":"string","example":"homePage"}}],"responses":{"200":{"description":"Component with fields","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Home Page"},"slug":{"type":"string","example":"homePage"},"project_id":{"type":"integer","example":1},"fields":{"type":"array","description":"Array of field definitions with values","items":{"type":"object","properties":{"key":{"type":"string","example":"hero"},"label":{"type":"string","example":"Hero Section"},"type":{"type":"string","enum":["text","textarea","rich-text","image","video","link","number","boolean","date","icon-key","group","list","simple-list"],"example":"group"},"value":{"description":"Field value (type depends on field type)","oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array"},{"type":"object"},{"type":"null"}]},"subFields":{"type":"array","description":"Nested fields for group type","items":{"type":"object"}},"items":{"type":"array","description":"Array items for list type","items":{"type":"object"}}}},"example":[{"key":"hero","label":"Hero Section","type":"group","subFields":[{"key":"title","label":"Title","type":"text","value":"Welcome to eTeam"},{"key":"image","label":"Background Image","type":"image","value":"https://example.com/hero.jpg"}]}]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component"}}}}}}}}},"/app-builder/components/by-repo":{"get":{"tags":["App Builder"],"summary":"Get component by GitHub repo name","description":"Retrieve component definition based on the GitHub repository name (hiq_repo)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"repoName","in":"query","description":"GitHub repository name (hiq_repo)","required":true,"schema":{"type":"string","example":"gais_humanizeiq_projectalpha_userservice"}}],"responses":{"200":{"description":"Component found","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":201},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"user-service"},"title":{"type":"string","example":"User Management Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Handles user auth and profile"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_userservice","description":"Auto-generated, immutable code"},"slug":{"type":"string","example":"user-service"},"hiq_repo":{"type":"string","example":"gais_humanizeiq_projectalpha_userservice"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["api.example.com"]},"ai_studio_link":{"type":"string","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","example":"web-dash"},"github_owner":{"type":"string","example":"org-name"}}}}}}}},"400":{"description":"Bad request - missing repoName","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"repoName query parameter is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found with the specified repo name."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component"}}}}}}}}},"/app-builder/components/by-github-repo":{"get":{"tags":["App Builder"],"summary":"Get component by GitHub repo name","description":"Retrieve component definition based on the GitHub repository name (github_repo field)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"githubRepo","in":"query","description":"GitHub repository name (github_repo)","required":true,"schema":{"type":"string","example":"web-dash"}}],"responses":{"200":{"description":"Component found","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","example":201},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"user-service"},"title":{"type":"string","example":"User Management Service"},"type":{"type":"string","enum":["UX","API"],"example":"UX"},"description":{"type":"string","example":"User interface dashboard"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_userservice","description":"Auto-generated, immutable code"},"slug":{"type":"string","example":"user-service"},"hiq_repo":{"type":"string","example":"gais_humanizeiq_projectalpha_userservice"},"github_repo":{"type":"string","example":"web-dash"},"github_owner":{"type":"string","example":"org-name"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*.example.com"]},"ai_studio_link":{"type":"string","example":"https://aistudio.google.com/..."}}}}}}}},"400":{"description":"Bad request - missing githubRepo","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"githubRepo query parameter is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found with the specified GitHub repo name."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component"}}}}}}}}},"/app-builder/components":{"get":{"tags":["App Builder"],"summary":"Get components for a project","description":"Retrieve all components for a specific project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"projectId","in":"query","description":"Project ID to filter components","required":true,"schema":{"type":"integer","example":101}}],"responses":{"200":{"description":"List of components","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":201},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"user-service"},"title":{"type":"string","example":"User Management Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Handles user auth and profile"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_userservice","description":"Auto-generated, immutable code: organizationname_projectname_slug"},"slug":{"type":"string","example":"user-service"},"hiq_repo":{"type":"string","example":"user-service-repo"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["api.example.com"]},"ai_studio_link":{"type":"string","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","example":"web-dash"},"github_owner":{"type":"string","example":"org-name"}}}}}}}}},"400":{"description":"Bad request - missing or invalid projectId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"projectId query parameter is required."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch components"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create a new component","description":"Create a new component within a specific project","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["projectId","name","title","type","additional_info"],"properties":{"projectId":{"type":"integer","example":101},"name":{"type":"string","example":"reporting-service"},"title":{"type":"string","example":"Reporting Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Generates reports"},"additional_info":{"type":"object","required":["slug","supported_domains","hiq_repo"],"properties":{"slug":{"type":"string","example":"reporting-service"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*"]},"hiq_repo":{"type":"string","example":"reporting-repo"},"ai_studio_link":{"type":"string","description":"UX components only","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","description":"UX components only","example":"web-dash"},"github_owner":{"type":"string","description":"UX components only","example":"org-name"}}}}}}}},"responses":{"201":{"description":"Component created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Component created successfully."},"component":{"type":"object","properties":{"id":{"type":"integer","example":203},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"reporting-service"},"title":{"type":"string","example":"Reporting Service"},"type":{"type":"string","example":"API"},"description":{"type":"string","example":"Generates reports"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Pending"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_reportingservice","description":"Auto-generated, immutable"},"slug":{"type":"string","example":"reporting-service"},"hiq_repo":{"type":"string","example":"reporting-repo"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*"]}}}}}}}}}},"400":{"description":"Bad request - missing required fields or invalid type","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project ID, name, title, and type are required."}}}}}},"404":{"description":"Project not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Project not found"}}}}}},"409":{"description":"Conflict - component with same name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component with this name already exists in this project"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create component"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update an existing component","description":"Update an existing component within a specific project. All fields except id are optional.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":203},"name":{"type":"string","example":"updated-service"},"title":{"type":"string","example":"Updated Service"},"type":{"type":"string","enum":["UX","API"],"example":"API"},"description":{"type":"string","example":"Updated description"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active","description":"Component status"},"additional_info":{"type":"object","properties":{"slug":{"type":"string","example":"updated-service"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*.example.com"]},"hiq_repo":{"type":"string","example":"updated-repo"},"ai_studio_link":{"type":"string","description":"UX components only","example":"https://aistudio.google.com/..."},"github_repo":{"type":"string","description":"UX components only","example":"web-dash"},"github_owner":{"type":"string","description":"UX components only","example":"org-name"}}}}}}}},"responses":{"200":{"description":"Component updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Component updated successfully."},"component":{"type":"object","properties":{"id":{"type":"integer","example":203},"project_id":{"type":"integer","example":101},"name":{"type":"string","example":"updated-service"},"title":{"type":"string","example":"Updated Service"},"type":{"type":"string","example":"API"},"description":{"type":"string","example":"Updated description"},"status":{"type":"string","enum":["Pending","Active","Inactive"],"example":"Active"},"created_at":{"type":"string","format":"date-time"},"additional_info":{"type":"object","properties":{"unique_app_code":{"type":"string","example":"humanizeiq_projectalpha_updatedservice","description":"Auto-generated, immutable (cannot be modified)"},"slug":{"type":"string","example":"updated-service"},"hiq_repo":{"type":"string","example":"updated-repo"},"supported_domains":{"type":"array","items":{"type":"string"},"example":["*.example.com"]}}}}}}}}}},"400":{"description":"Bad request - missing component ID or invalid type","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component ID is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found"}}}}}},"409":{"description":"Conflict - component name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component with this name already exists in this project"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update component"}}}}}}}}},"/app-builder/components/{componentId}/invite-collaborator":{"post":{"tags":["App Builder"],"summary":"Invite user as GitHub collaborator","description":"Invite the authenticated user as a collaborator on the component's GitHub repository using the repository owner's credentials","security":[{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"path","description":"Component ID","required":true,"schema":{"type":"integer","example":36}}],"responses":{"200":{"description":"Collaborator invited successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Invitation sent to username"},"invited":{"type":"boolean","example":true},"alreadyCollaborator":{"type":"boolean","example":false},"repository":{"type":"string","example":"hiq-automation/craft-taskmanager"},"collaborator":{"type":"string","example":"github-username"}}}}}},"400":{"description":"Bad request - missing configuration or credentials","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component does not have a GitHub repository configured"}}}}}},"401":{"description":"Unauthorized - authentication required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Authentication required"}}}}}},"404":{"description":"Component or credentials not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found"}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to invite collaborator"},"details":{"type":"string","example":"GitHub API error: 403 - ..."}}}}}}}}},"/app-builder/requirements":{"get":{"tags":["App Builder"],"summary":"Get requirements for a component","description":"Retrieve all requirements for a specific component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"query","description":"Component ID","required":true,"schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Requirements retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","enum":["Feature","Defect","Enhancement","Task"],"example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","enum":["New","Open","InProgress","Resolved","Closed"],"example":"Open"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required and must be a valid number."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch requirements"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Save requirements","description":"Create new or update existing requirements for a component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","requirements"],"properties":{"componentId":{"type":"integer","example":201,"description":"Component ID"},"requirements":{"type":"array","description":"Array of requirements to save","items":{"type":"object","required":["type","title","status"],"properties":{"id":{"type":"integer","example":301,"description":"Optional - include for updates, omit for new requirements"},"type":{"type":"string","enum":["Feature","Defect","Enhancement","Task"],"example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","enum":["New","Open","InProgress","Resolved","Closed"],"example":"Open"}}}}}}}}},"responses":{"200":{"description":"Requirements saved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Requirements saved successfully."},"requirements":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user..."},"status":{"type":"string","example":"Open"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Each requirement must have type, title, and status."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to save requirements"}}}}}}}}},"/app-builder/requirements/by-hash":{"get":{"tags":["App Builder"],"summary":"Get requirement by unique hash","description":"Retrieve a specific requirement using its unique hash identifier (SHA-256 hash of componentId-appCode-requirementId)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"hash","in":"query","description":"Unique hash identifier for the requirement","required":true,"schema":{"type":"string","example":"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"}}],"responses":{"200":{"description":"Requirement retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"requirement":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","example":"Open"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing hash parameter","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"hash query parameter is required."}}}}}},"404":{"description":"Requirement not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Requirement not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch requirement"}}}}}}}}},"/app-builder/requirements/backfill-hashes":{"post":{"tags":["App Builder"],"summary":"Backfill unique hashes for requirements","description":"Generates and updates unique_hash for all requirements that don't have it populated. This is useful for migrating existing data after adding the unique_hash feature.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Backfill completed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"processed":{"type":"integer","example":25,"description":"Total requirements processed"},"updated":{"type":"integer","example":23,"description":"Requirements successfully updated with hash"},"skipped":{"type":"integer","example":2,"description":"Requirements skipped due to errors"},"message":{"type":"string","example":"Backfill complete: 23 updated, 2 skipped"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to backfill requirement hashes"},"details":{"type":"string","example":"Database connection error"}}}}}}}}},"/app-builder/requirements/{id}":{"patch":{"tags":["App Builder"],"summary":"Update a requirement","description":"Update individual fields of a requirement. Only the fields provided in the request body will be updated.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","description":"Requirement ID","required":true,"schema":{"type":"integer","example":301}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["Feature","Defect","Enhancement","Task"],"example":"Feature","description":"Optional - Requirement type"},"title":{"type":"string","example":"Updated User Login","description":"Optional - Requirement title"},"description":{"type":"string","example":"Updated description for user login","description":"Optional - Requirement description"},"status":{"type":"string","enum":["New","Open","InProgress","Resolved","Closed"],"example":"Resolved","description":"Optional - Requirement status"}},"description":"At least one field must be provided"},"examples":{"Update status only":{"value":{"status":"Resolved"}},"Update multiple fields":{"value":{"status":"InProgress","description":"Working on implementing OAuth2 authentication"}}}}}},"responses":{"200":{"description":"Requirement updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Requirement updated successfully."},"requirement":{"type":"object","properties":{"id":{"type":"integer","example":301},"component_id":{"type":"integer","example":201},"type":{"type":"string","example":"Feature"},"title":{"type":"string","example":"User Login"},"description":{"type":"string","example":"As a user, I want to login..."},"status":{"type":"string","example":"Resolved"},"unique_hash":{"type":"string","example":"a1b2c3d4e5f6..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"No valid fields provided for update. Allowed fields: type, title, description, status"}}}}}},"404":{"description":"Requirement not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Requirement not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update requirement"},"details":{"type":"string","example":"Database error"}}}}}}}}},"/app-builder/get_requirements_from_code":{"get":{"tags":["App Builder"],"summary":"Get file from code repository","description":"Fetches a file from a specified folder in the Gitea repository (playtest branch)","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"hiq_repo","in":"query","description":"Gitea repository name in owner/repo format (e.g., HumanizeIQ/gais_myapp)","required":true,"schema":{"type":"string","example":"HumanizeIQ/gais_myapp"}},{"name":"folder","in":"query","description":"Folder path in the repository (optional, defaults to root)","required":false,"schema":{"type":"string","example":"docs"}},{"name":"file_name","in":"query","description":"Name of the file to fetch (optional, defaults to requirements.md)","required":false,"schema":{"type":"string","example":"requirements.md"}}],"responses":{"200":{"description":"File retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"content":{"type":"string","example":"# Requirements\n\n## Feature 1\n..."},"sha":{"type":"string","example":"abc123..."},"size":{"type":"integer","example":1024},"path":{"type":"string","example":"docs/requirements.md"},"name":{"type":"string","example":"requirements.md"}}}}}},"400":{"description":"Bad request - missing or invalid hiq_repo","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"hiq_repo query parameter is required."}}}}}},"404":{"description":"File not found in repository","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"File 'docs/requirements.md' not found in repository."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch file from repository."}}}}}}}}},"/app-builder/create-pull-request":{"post":{"tags":["App Builder"],"summary":"Create a pull request in Gitea","description":"Creates a pull request from playtest to dev or dev to main in the component's Gitea repository","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","targetBranch"],"properties":{"componentId":{"type":"integer","description":"Component ID","example":201},"targetBranch":{"type":"string","enum":["dev","main"],"description":"Target branch for the pull request (dev or main). Source branch is automatically determined: playtest→dev or dev→main","example":"dev"},"comment":{"type":"string","description":"Optional comment/description for the pull request","example":"Merging latest features from playtest"}}}}}},"responses":{"201":{"description":"Pull request created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Pull request created successfully."},"pullRequest":{"type":"object","properties":{"id":{"type":"integer","example":42},"number":{"type":"integer","example":5},"title":{"type":"string","example":"Merge playtest into dev"},"state":{"type":"string","example":"open"},"url":{"type":"string","example":"https://git.code.svchub.com/HumanizeIQ/gais_myapp/pulls/5"},"sourceBranch":{"type":"string","example":"playtest"},"targetBranch":{"type":"string","example":"dev"},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid parameters","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"targetBranch must be either \"dev\" or \"main\"."}}}}}},"404":{"description":"Component, repository, or branch not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"409":{"description":"Pull request already exists or branches are identical","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Pull request already exists or branches are identical."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create pull request."}}}}}}}}},"/app-builder/prompts":{"get":{"tags":["App Builder"],"summary":"Get system AI prompts","description":"Retrieves the AI system prompts configuration from the server","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"System prompts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"documentation_assistant":{"type":"string"},"ideation_product_manager":{"type":"string"},"design_architect":{"type":"string"},"requirements_product_owner":{"type":"string"},"builder_prompt_engineer":{"type":"string"},"requirements_generator_modal":{"type":"string"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch system prompts"}}}}}}}}},"/app-builder/component-prompts":{"get":{"tags":["App Builder"],"summary":"Get component prompts","description":"Retrieves all prompts associated with a specific component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"query","required":true,"description":"Component ID","schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Component prompts retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":401},"component_id":{"type":"integer","example":201},"title":{"type":"string","example":"System Prompt"},"description":{"type":"string","example":"Core system instruction"},"type":{"type":"string","enum":["System","Other"],"example":"System"},"content":{"type":"string","example":"You are an AI..."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component prompts"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create component prompt","description":"Creates a new prompt for a component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","title","type","content"],"properties":{"componentId":{"type":"integer","example":201},"title":{"type":"string","example":"New Prompt"},"description":{"type":"string","example":"Prompt description"},"type":{"type":"string","enum":["System","Other"],"example":"Other"},"content":{"type":"string","example":"You are an AI assistant..."}}}}}},"responses":{"201":{"description":"Prompt created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Prompt created successfully."},"prompt":{"type":"object","properties":{"id":{"type":"integer","example":402},"component_id":{"type":"integer","example":201},"title":{"type":"string","example":"New Prompt"},"description":{"type":"string","example":"Desc"},"type":{"type":"string","example":"Other"},"content":{"type":"string","example":"Content"},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"title is required."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create prompt"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update component prompt","description":"Updates an existing component prompt","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":402},"title":{"type":"string","example":"Updated Title"},"description":{"type":"string","example":"Updated description"},"type":{"type":"string","enum":["System","Other"],"example":"Other"},"content":{"type":"string","example":"Updated content"}}}}}},"responses":{"200":{"description":"Prompt updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Prompt updated successfully."},"prompt":{"type":"object","properties":{"id":{"type":"integer","example":402},"component_id":{"type":"integer","example":201},"title":{"type":"string","example":"Updated Title"},"description":{"type":"string","example":"Desc"},"type":{"type":"string","example":"Other"},"content":{"type":"string","example":"Content"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - missing or invalid data","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"id is required."}}}}}},"404":{"description":"Prompt not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Prompt not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update prompt"}}}}}}}},"delete":{"tags":["App Builder"],"summary":"Delete component prompt","description":"Deletes a component prompt","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"query","required":true,"description":"Prompt ID","schema":{"type":"integer","example":402}}],"responses":{"200":{"description":"Prompt deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Prompt deleted successfully."}}}}}},"400":{"description":"Bad request - missing or invalid id","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"id query parameter is required."}}}}}},"404":{"description":"Prompt not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Prompt not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to delete prompt"}}}}}}}}},"/app-builder/component-content":{"get":{"tags":["App Builder"],"summary":"Get component content items","description":"Retrieve all content items for a specific component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"componentId","in":"query","description":"Component ID","required":true,"schema":{"type":"integer","example":201}}],"responses":{"200":{"description":"Content items retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":501},"component_id":{"type":"integer","example":201},"text_id":{"type":"string","example":"app_verify_access"},"description":{"type":"string","example":"Loading text..."},"text":{"type":"string","example":"Verifying access..."},"format":{"type":"string","enum":["plain","markdown"],"example":"plain"},"type":{"type":"string","enum":["Title","Body","LinkText","Heading"],"example":"Body"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"400":{"description":"Bad request - missing or invalid componentId","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"componentId query parameter is required and must be a valid number."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch component content"}}}}}}}},"post":{"tags":["App Builder"],"summary":"Create component content item","description":"Create a new content item for a component","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["componentId","textId","text","format","type"],"properties":{"componentId":{"type":"integer","example":201,"description":"Component ID"},"textId":{"type":"string","example":"welcome_msg","description":"Unique identifier for the content item"},"description":{"type":"string","example":"Welcome message","description":"Optional description of the content"},"text":{"type":"string","example":"Hello User!","description":"The actual content text"},"format":{"type":"string","enum":["plain","markdown"],"example":"plain","description":"Content format"},"type":{"type":"string","enum":["Title","Body","LinkText","Heading"],"example":"Body","description":"Content type"}}}}}},"responses":{"201":{"description":"Content item created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Content item created successfully."},"item":{"type":"object","properties":{"id":{"type":"integer","example":502},"component_id":{"type":"integer","example":201},"text_id":{"type":"string","example":"welcome_msg"},"description":{"type":"string","example":"Welcome message"},"text":{"type":"string","example":"Hello User!"},"format":{"type":"string","example":"plain"},"type":{"type":"string","example":"Body"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"textId is required and must be a non-empty string."}}}}}},"404":{"description":"Component not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Component not found."}}}}}},"409":{"description":"Conflict - content item with this textId already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Content item with this text_id already exists for this component"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create content item"}}}}}}}},"patch":{"tags":["App Builder"],"summary":"Update component content item","description":"Update an existing content item. Only the fields provided in the request body will be updated.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer","example":502,"description":"Content item ID"},"textId":{"type":"string","example":"updated_welcome_msg","description":"Optional - Updated text identifier"},"description":{"type":"string","example":"Updated description","description":"Optional - Updated description"},"text":{"type":"string","example":"Welcome back!","description":"Optional - Updated content text"},"format":{"type":"string","enum":["plain","markdown"],"example":"markdown","description":"Optional - Updated format"},"type":{"type":"string","enum":["Title","Body","LinkText","Heading"],"example":"Title","description":"Optional - Updated type"}}},"examples":{"Update text only":{"value":{"id":502,"text":"Updated welcome message"}},"Update multiple fields":{"value":{"id":502,"text":"Updated text","format":"markdown","type":"Heading"}}}}}},"responses":{"200":{"description":"Content item updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Content item updated successfully."},"item":{"type":"object","properties":{"id":{"type":"integer","example":502},"component_id":{"type":"integer","example":201},"text_id":{"type":"string","example":"welcome_msg"},"description":{"type":"string","example":"Updated description"},"text":{"type":"string","example":"Welcome back!"},"format":{"type":"string","example":"markdown"},"type":{"type":"string","example":"Title"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"No valid fields provided for update. Allowed fields: textId, description, text, format, type"}}}}}},"404":{"description":"Content item not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Content item not found."}}}}}},"409":{"description":"Conflict - content item with this textId already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Content item with this text_id already exists for this component"}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update content item"},"details":{"type":"string","example":"Database error"}}}}}}}}},"/work-management/tasks":{"get":{"tags":["Work Management"],"summary":"Get tasks for authenticated user","description":"Retrieve all tasks assigned to the authenticated user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Tasks retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":601},"title":{"type":"string","example":"Complete Design Doc"},"description":{"type":"string","example":"Finalize the architecture diagram."},"assignee":{"type":"string","example":"User Name"},"assigned_date":{"type":"string","format":"date-time","example":"2025-01-01T10:00:00Z"},"due_date":{"type":"string","format":"date-time","example":"2025-01-05T17:00:00Z"},"start_date":{"type":"string","format":"date-time","nullable":true,"example":null},"close_date":{"type":"string","format":"date-time","nullable":true,"example":null},"status":{"type":"string","enum":["Assigned","Open","InProgress","Closed"],"example":"Assigned"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}},"401":{"description":"Unauthorized - user not authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"User not authenticated."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to fetch tasks"}}}}}}}},"post":{"tags":["Work Management"],"summary":"Create a new task","description":"Create a new task. The assignedDate is automatically set to the current timestamp.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","assignee","dueDate","organizationId","projectId"],"properties":{"title":{"type":"string","example":"New Task","description":"Task title"},"description":{"type":"string","example":"Task description","description":"Optional task description"},"assignee":{"type":"string","description":"User ID (uid) of the person assigned to this task","example":"426bcea5-adb9-4580-a8ca-3a40fdb0ef85"},"dueDate":{"type":"string","format":"date-time","example":"2025-01-10T17:00:00Z","description":"Task due date (ISO 8601 format)"},"organizationId":{"type":"integer","example":1,"description":"Organization ID"},"projectId":{"type":"integer","example":1,"description":"Project ID"}}}}}},"responses":{"201":{"description":"Task created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Task created successfully."},"task":{"type":"object","properties":{"id":{"type":"integer","example":602},"title":{"type":"string","example":"New Task"},"description":{"type":"string","example":"Desc"},"assignee":{"type":"string","example":"Assignee Name"},"assigned_date":{"type":"string","format":"date-time","example":"2025-01-02T12:00:00Z"},"due_date":{"type":"string","format":"date-time","example":"2025-01-10T17:00:00Z"},"start_date":{"type":"string","nullable":true,"example":null},"close_date":{"type":"string","nullable":true,"example":null},"status":{"type":"string","example":"Open"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"title is required and must be a non-empty string."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to create task"}}}}}}}}},"/work-management/tasks/{id}/status":{"patch":{"tags":["Work Management"],"summary":"Update task status","description":"Update the status of a task. Backend automatically sets startDate when status changes to InProgress and closeDate when status changes to Closed.","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","description":"Task ID","required":true,"schema":{"type":"integer","example":601}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["Assigned","Open","InProgress","Closed"],"example":"InProgress","description":"New task status"}}},"examples":{"Start task":{"value":{"status":"InProgress"}},"Close task":{"value":{"status":"Closed"}},"Reopen task":{"value":{"status":"Open"}}}}}},"responses":{"200":{"description":"Task status updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Task status updated."},"task":{"type":"object","properties":{"id":{"type":"integer","example":601},"title":{"type":"string","example":"Complete Design Doc"},"description":{"type":"string","example":"Finalize the architecture diagram."},"assignee":{"type":"string","example":"User Name"},"assigned_date":{"type":"string","format":"date-time"},"due_date":{"type":"string","format":"date-time"},"start_date":{"type":"string","format":"date-time","example":"2025-01-03T09:00:00Z"},"close_date":{"type":"string","nullable":true,"example":null},"status":{"type":"string","example":"InProgress"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad request - validation error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"status is required and must be one of: Open, InProgress, Closed"}}}}}},"404":{"description":"Task not found","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Task not found."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Failed to update task status"},"details":{"type":"string","example":"Database error"}}}}}}}}},"/rbac/roles":{"get":{"tags":["RBAC"],"summary":"Get all roles","description":"Retrieve all roles in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Roles retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Admin"},"description":{"type":"string","example":"Administrator role"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Create a new role","description":"Create a new role in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"Editor"},"description":{"type":"string","example":"Can edit content"}}}}}},"responses":{"201":{"description":"Role created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"message":{"type":"string","example":"Role created successfully."},"role":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}},"409":{"description":"Role already exists","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"message":{"type":"string","example":"Role with this name already exists."}}}}}}}}},"/rbac/roles/{roleId}":{"delete":{"tags":["RBAC"],"summary":"Delete a role","description":"Delete a role from the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Role deleted successfully"},"404":{"description":"Role not found"}}}},"/rbac/permissions":{"get":{"tags":["RBAC"],"summary":"Get all permissions","description":"Retrieve all permissions in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"Permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"create:component"},"description":{"type":"string","example":"Can create components"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Create a new permission","description":"Create a new permission in the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"delete:component"},"description":{"type":"string","example":"Can delete components"}}}}}},"responses":{"201":{"description":"Permission created successfully"},"409":{"description":"Permission already exists"}}}},"/rbac/permissions/{permissionId}":{"delete":{"tags":["RBAC"],"summary":"Delete a permission","description":"Delete a permission from the system","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"permissionId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Permission deleted successfully"},"404":{"description":"Permission not found"}}}},"/rbac/roles/{roleId}/permissions":{"get":{"tags":["RBAC"],"summary":"Get permissions for a role","description":"Retrieve all permissions assigned to a specific role","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Add permission to role","description":"Assign a permission to a role","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["permissionId"],"properties":{"permissionId":{"type":"integer","example":1}}}}}},"responses":{"200":{"description":"Permission added to role successfully"},"404":{"description":"Role or permission not found"}}}},"/rbac/roles/{roleId}/permissions/{permissionId}":{"delete":{"tags":["RBAC"],"summary":"Remove permission from role","description":"Remove a permission from a role","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}},{"name":"permissionId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Permission removed from role successfully"}}}},"/rbac/users/{userId}/roles":{"get":{"tags":["RBAC"],"summary":"Get user roles","description":"Retrieve all roles assigned to a user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User roles retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}}},"post":{"tags":["RBAC"],"summary":"Add user to role","description":"Assign a role to a user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roleId"],"properties":{"roleId":{"type":"integer","example":1}}}}}},"responses":{"200":{"description":"User added to role successfully"},"404":{"description":"User or role not found"}}}},"/rbac/users/{userId}/roles/{roleId}":{"delete":{"tags":["RBAC"],"summary":"Remove user from role","description":"Remove a role from a user","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"name":"roleId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"User removed from role successfully"}}}},"/rbac/users/{userId}/permissions":{"get":{"tags":["RBAC"],"summary":"Get user permissions","description":"Retrieve all unique permissions for a user across all their roles","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}}}}},"/rbac/me/roles":{"get":{"tags":["RBAC"],"summary":"Get current user roles and permissions","description":"Retrieve role names and permission names for the currently authenticated user (from decrypted cookie)","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Current user roles and permissions retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"string"},"example":["Admin","Editor"]},"permissions":{"type":"array","items":{"type":"string"},"example":["create:component","delete:component","edit:component"]}}}}}},"401":{"description":"User not authenticated or using API key"}}}},"/rbac/me/permissions":{"get":{"tags":["RBAC"],"summary":"Get current user permissions","description":"Retrieve all unique permissions for the currently authenticated user across all their roles (from decrypted cookie)","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Current user permissions retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":"string"}}}}}}},"401":{"description":"User not authenticated or using API key"}}}},"/rbac/me":{"get":{"tags":["RBAC"],"summary":"Get current user complete profile","description":"Retrieve roles, permissions, and preferences for the currently authenticated user","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"User profile retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"string"},"example":["Admin","Editor"]},"permissions":{"type":"array","items":{"type":"string"},"example":["create:component","delete:component"]},"preferences":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string"},"app_id":{"type":"string"},"object_id":{"type":"string"},"object_type":{"type":"string"}}}}}}}}},"401":{"description":"User not authenticated or using API key"}}}},"/rbac/preferences":{"get":{"tags":["RBAC"],"summary":"Get all user preferences","description":"Retrieve all preferences for the currently authenticated user","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Preferences retrieved successfully"}}},"post":{"tags":["RBAC"],"summary":"Save user preference","description":"Save or update a preference for the currently authenticated user","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appId"],"properties":{"appId":{"type":"string","example":"my-app"},"objectId":{"type":"string","example":"obj-123"},"objectType":{"type":"string","example":"dashboard"}}}}}},"responses":{"200":{"description":"Preference saved successfully"}}}},"/rbac/preferences/{appId}":{"get":{"tags":["RBAC"],"summary":"Get preference for specific app","description":"Retrieve preference for a specific app for the currently authenticated user","security":[{"BearerAuth":[]}],"parameters":[{"name":"appId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Preference retrieved successfully"},"404":{"description":"Preference not found"}}}}}} \ No newline at end of file diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx new file mode 100644 index 0000000..f125d80 --- /dev/null +++ b/components/Dashboard.tsx @@ -0,0 +1,106 @@ + +import React, { useEffect, useState } from 'react'; +import { AppMetadata } from '../types'; +import { geminiService } from '../services/geminiService'; + +interface DashboardProps { + metadata: AppMetadata; +} + +export const Dashboard: React.FC = ({ metadata }) => { + const [greeting, setGreeting] = useState('Loading welcome message...'); + const [features, setFeatures] = useState([]); + const [isAiLoading, setIsAiLoading] = useState(true); + + useEffect(() => { + const initializeApp = async () => { + setIsAiLoading(true); + try { + const [aiGreeting, aiFeatures] = await Promise.all([ + geminiService.getAppContextualGreeting(metadata), + geminiService.getSmartFeatureIdeas(metadata) + ]); + setGreeting(aiGreeting); + setFeatures(aiFeatures); + } catch (error) { + console.error("AI Initialization failed", error); + setGreeting(`Welcome to ${metadata.name}`); + } finally { + setIsAiLoading(false); + } + }; + + initializeApp(); + }, [metadata]); + + return ( +
+
+
+
+

Identity

+

{metadata.name}

+

{metadata.description}

+
+
+ +
+
+

+ + + + + + Smart Assistant Greeting +

+
+ "{greeting}" +
+
+ +
+

+ + + + + + Dynamic Smart Features +

+
    + {isAiLoading ? ( + [1, 2, 3].map(i =>
  • ) + ) : ( + features.map((feature, idx) => ( +
  • +
    + {feature} +
  • + )) + )} +
+
+
+ +
+

Core Capabilities

+
+ {[ + { label: 'Data Tracking', icon: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z' }, + { label: 'Cloud Sync', icon: 'M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z' }, + { label: 'AI Optimization', icon: 'M13 10V3L4 14h7v7l9-11h-7z' }, + { label: 'Privacy First', icon: 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z' }, + ].map((item, idx) => ( +
+ + + +
{item.label}
+
+ ))} +
+
+
+ ); +}; diff --git a/components/Layout.tsx b/components/Layout.tsx new file mode 100644 index 0000000..126a38b --- /dev/null +++ b/components/Layout.tsx @@ -0,0 +1,43 @@ + +import React from 'react'; + +interface LayoutProps { + children: React.ReactNode; + title: string; +} + +export const Layout: React.FC = ({ children, title }) => { + return ( +
+
+
+
+
+
+ {title.charAt(0)} +
+

+ {title === 'NOTSET' ? 'App Template' : title} +

+
+ +
+
+
+
+
+ {children} +
+
+
+
+ Powered by Gemini AI & Dynamic Metadata Template +
+
+
+ ); +}; diff --git a/components/SetupPrompt.tsx b/components/SetupPrompt.tsx new file mode 100644 index 0000000..fc322d2 --- /dev/null +++ b/components/SetupPrompt.tsx @@ -0,0 +1,36 @@ + +import React from 'react'; + +export const SetupPrompt: React.FC = () => { + return ( +
+
+ + + +
+

Configuration Required

+

+ The application metadata is currently set to NOTSET. + Please update the metadata.json file with your app's name and description to proceed. +

+ +
+
+
+
+
+ metadata.json +
+
{`{
+  "name": "Your Awesome App",
+  "description": "A powerful tool to help users manage..."
+}`}
+
+ +

+ The AI will automatically generate features and interfaces based on your definition once you reload. +

+
+ ); +}; diff --git a/components/icons.tsx b/components/icons.tsx new file mode 100644 index 0000000..ccd00c9 --- /dev/null +++ b/components/icons.tsx @@ -0,0 +1,172 @@ + +import React from 'react'; + +export const SunIcon: React.FC = () => ( + + + +); + +export const MoonIcon: React.FC = () => ( + + + +); + +export const PaperAirplaneIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const PaperClipIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const XMarkIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const GithubIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ExternalLinkIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const CodeIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const EditIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const SparklesIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const CheckCircleIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const EyeIcon: React.FC<{className?: string}> = ({ className }) => ( + + + + +); + +export const UploadIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const DownloadIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ClipboardIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ListBulletIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const DocumentTextIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const PrinterIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const CommandLineIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const UserPlusIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const FolderIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const PlusIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ChatBubbleLeftIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ChevronRightIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const ChevronDownIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const TrashIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const BookOpenIcon: React.FC<{className?: string}> = ({ className }) => ( + + + +); + +export const Cog6ToothIcon: React.FC<{className?: string}> = ({ className }) => ( + + + + +); diff --git a/containers/MainContainer.tsx b/containers/MainContainer.tsx new file mode 100644 index 0000000..721998d --- /dev/null +++ b/containers/MainContainer.tsx @@ -0,0 +1,44 @@ +import React, { useState, useEffect } from 'react'; +import { useTheme } from '../contexts/ThemeContext'; +import { User } from '../types'; +import MainLayout from './layout/MainLayout'; + +interface MainContainerProps { + user: User | null; + workspaceUrl: string; + signOutUrl: string; + children?: React.ReactNode; +} + +/** + * MainContainer handles layout logic: + * - Theme state access + * - App version fetching + */ +const MainContainer: React.FC = (props) => { + const { theme, toggleTheme } = useTheme(); + const [version, setVersion] = useState(null); + + useEffect(() => { + fetch('./version.json') + .then(response => { + if (response.ok) return response.json(); + return null; + }) + .then(data => { + if (data && data.version) setVersion(data.version); + }) + .catch(() => {}); + }, []); + + return ( + + ); +}; + +export default MainContainer; \ No newline at end of file diff --git a/containers/layout/MainLayout.tsx b/containers/layout/MainLayout.tsx new file mode 100644 index 0000000..74a67f5 --- /dev/null +++ b/containers/layout/MainLayout.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { User } from '../../types'; +import Header from './components/Header'; +import Footer from './components/Footer'; + +interface MainLayoutProps { + user: User | null; + workspaceUrl: string; + signOutUrl: string; + theme: 'light' | 'dark'; + toggleTheme: () => void; + version: string | null; + children?: React.ReactNode; +} + +const MainLayout: React.FC = ({ + user, + workspaceUrl, + signOutUrl, + theme, + toggleTheme, + version, + children +}) => { + return ( +
+
+ +
+ {children || ( +
+

Framework Ready

+

The skeleton is successfully initialized.

+
+ )} +
+ +
+
+ ); +}; + +export default MainLayout; \ No newline at end of file diff --git a/containers/layout/components/Footer.tsx b/containers/layout/components/Footer.tsx new file mode 100644 index 0000000..86397c9 --- /dev/null +++ b/containers/layout/components/Footer.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +interface FooterProps { + version: string | null; +} + +const Footer: React.FC = ({ version }) => { + return ( +
+

© 2025 HumanizeIQ. All Rights Reserved. {version && `v${version}`}

+
+ ); +}; + +export default Footer; \ No newline at end of file diff --git a/containers/layout/components/Header.tsx b/containers/layout/components/Header.tsx new file mode 100644 index 0000000..b9cad23 --- /dev/null +++ b/containers/layout/components/Header.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { User } from '../../../types'; +import { SunIcon, MoonIcon } from '../../../components/icons'; + +interface HeaderProps { + user: User | null; + workspaceUrl: string; + signOutUrl: string; + theme: 'light' | 'dark'; + toggleTheme: () => void; +} + +const Header: React.FC = ({ user, workspaceUrl, signOutUrl, theme, toggleTheme }) => { + return ( +
+
+ HumanizeIQ Logo +

Aura Craft Studio

+
+ + +
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/content.json b/content.json new file mode 100644 index 0000000..883094f --- /dev/null +++ b/content.json @@ -0,0 +1,59 @@ + +[ + { + "textId": "app_verify_access", + "description": "Loading text while verifying authentication", + "text": "Verifying access...", + "format": "plain", + "type": "Body" + }, + { + "textId": "app_redirect_login", + "description": "Loading text while redirecting to login", + "text": "Redirecting to login...", + "format": "plain", + "type": "Body" + }, + { + "textId": "app_unauthorized_title", + "description": "Title for unauthorized access screen", + "text": "Unauthorized", + "format": "plain", + "type": "Title" + }, + { + "textId": "app_unauthorized_desc", + "description": "Description for unauthorized access screen", + "text": "You do not have permission to access this application.", + "format": "plain", + "type": "Body" + }, + { + "textId": "header_title", + "description": "Main application title in header", + "text": "Aura Craft Studio Template", + "format": "plain", + "type": "Title" + }, + { + "textId": "nav_workspace", + "description": "Navigation link to workspace", + "text": "My Workspace", + "format": "plain", + "type": "LinkText" + }, + { + "textId": "nav_signout", + "description": "Navigation link to sign out", + "text": "Sign out", + "format": "plain", + "type": "LinkText" + }, + { + "textId": "footer_copyright", + "description": "Footer copyright text", + "text": "© 2025 HumanizeIQ. All Rights Reserved.", + "format": "plain", + "type": "Body" + } +] diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx new file mode 100644 index 0000000..652ce14 --- /dev/null +++ b/contexts/AuthContext.tsx @@ -0,0 +1,51 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import { User, Role, Permission } from '../types'; +import { useAuthSession } from '../hooks/useAuthSession'; + +interface AuthContextType { + authState: 'checking' | 'authorized' | 'unauthorized'; + user: User | null; + roles: Role[]; + permissions: Permission[]; + workspaceUrl: string; + signOutUrl: string; +} + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const { authState, user, roles, permissions } = useAuthSession(); + const [workspaceUrl, setWorkspaceUrl] = useState('/home/workspace'); + const [signOutUrl, setSignOutUrl] = useState('/home/confirm-signout'); + + useEffect(() => { + const hostname = window.location.hostname; + if (hostname.startsWith('tools.')) { + const newHost = hostname.replace('tools.', 'www.'); + const protocol = window.location.protocol; + setWorkspaceUrl(`${protocol}//${newHost}/home/workspace`); + setSignOutUrl(`${protocol}//${newHost}/home/confirm-signout`); + } + }, []); + + return ( + + {children} + + ); +}; + +export const useAuthContext = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuthContext must be used within an AuthProvider'); + } + return context; +}; \ No newline at end of file diff --git a/contexts/ThemeContext.tsx b/contexts/ThemeContext.tsx new file mode 100644 index 0000000..342eab6 --- /dev/null +++ b/contexts/ThemeContext.tsx @@ -0,0 +1,42 @@ +import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; +import { useLocalStorage } from '../hooks/useLocalStorage'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; +} + +const ThemeContext = createContext(undefined); + +export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [theme, setTheme] = useLocalStorage('theme', 'light'); + + useEffect(() => { + const root = window.document.documentElement; + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + }, [theme]); + + const toggleTheme = () => { + setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); + }; + + return ( + + {children} + + ); +}; + +export const useTheme = (): ThemeContextType => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; diff --git a/craft.txt b/craft.txt new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..99b5f3b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + aura_central: + build: + context: . + dockerfile: Dockerfile + command: sh -lc "if [ ! -d node_modules ] || [ -z \"$(ls -A node_modules 2>/dev/null)\" ] || [ ! -d node_modules/@rollup/rollup-linux-arm64-musl ]; then npm ci --include=optional; fi; npm run dev" + ports: + - "3000:3000" + volumes: + - .:/app + - /app/node_modules + environment: + - GEMINI_API_KEY=${GEMINI_API_KEY} + stdin_open: true + tty: true diff --git a/docs/blueprint.md b/docs/blueprint.md new file mode 100644 index 0000000..fbd328e --- /dev/null +++ b/docs/blueprint.md @@ -0,0 +1,24 @@ +# Aura Central App Blueprint + +## Overview +Aura Central is a "Central Nervous System" application designed to be a personal AI assistant and productivity dashboard. It features a modern, high-density interface with real-time updates and multi-modal interaction (Chat, Voice, Meet). + +## Core Features +- **Multi-step Authentication**: A secure, invite-only onboarding process with email, phone, and 2FA (TOTP) verification. +- **Subscription Management**: A billing flow for managing AI credits and plan levels. +- **Intelligent Dashboard**: A centralized hub for tasks, schedule, files, and recent communications. +- **Multi-modal Assistant (Ask Aura)**: + - **Chat**: Text-based interaction with Gemini AI. + - **Voice**: Real-time voice interaction. + - **Meet**: Meeting transcription and summarization. +- **Contextual View Modes**: Toggle between "Professional" and "Personal" modes to filter relevant tasks and information. + +## Technical Stack +- **Frontend**: React with TypeScript. +- **Styling**: Tailwind CSS with custom theme variables. +- **Animations**: Framer Motion (motion/react). +- **Backend/Database**: Firebase (Firestore & Authentication). +- **AI Integration**: Google Gemini API via `@google/genai`. + +## Architecture +The app follows a Single Page Application (SPA) architecture. The main `App.tsx` handles the core layout and state management, while specialized components handle the authentication and subscription flows. Styling is modularized into several CSS files to separate concerns (theme, layout, components, etc.). diff --git a/docs/filespec.md b/docs/filespec.md new file mode 100644 index 0000000..6c86a93 --- /dev/null +++ b/docs/filespec.md @@ -0,0 +1,32 @@ +# Aura Central File Specification + +## Root Directory +- **.env.example**: Template for environment variables (e.g., Gemini API Key). +- **.gitignore**: Specifies files and directories to be ignored by Git. +- **firebase-applet-config.json**: Configuration for the Firebase project (API keys, project IDs). +- **firebase-blueprint.json**: Intermediate representation of the Firestore data structure. +- **firestore.rules**: Security rules for the Firestore database. +- **index.html**: The main entry point for the browser. +- **metadata.json**: Application metadata (name, description, permissions). +- **package.json**: Manages npm dependencies and scripts. +- **tsconfig.json**: TypeScript compiler configuration. +- **vite.config.ts**: Configuration for the Vite build tool. + +## /src Directory +- **App.tsx**: The main application component. It handles the core dashboard layout, state management, and view mode toggling. +- **firebase.ts**: Initializes the Firebase SDK and exports the database and authentication instances. +- **index.css**: The main entry point for CSS, importing all modular style files. +- **main.tsx**: The entry point for the React application, rendering the `App` component. + +## /src/components Directory +- **AuthFlow.tsx**: Implements the multi-step onboarding process (Welcome, Invite Code, Email, Phone, 2FA). +- **SubscriptionFlow.tsx**: Handles the billing and credit top-up process for Aura Pro subscriptions. + +## /src/styles Directory +- **aura.css**: Styles specific to the "Ask Aura" assistant (chat bubbles, suggestion pills, voice/meet circles). +- **auth.css**: Styles for the authentication and onboarding flow. +- **base.css**: Global base styles, resets, and custom scrollbar styling. +- **components.css**: Reusable UI component styles (cards, inputs, buttons, badges) defined as Tailwind utilities. +- **layout.css**: Styles for the main dashboard grid, header, and toolbar. +- **theme.css**: Defines the color palette, CSS variables, and dark mode configuration. +- **utilities.css**: Additional custom utility classes for the application. diff --git a/features/workspace/WorkspaceContainer.tsx b/features/workspace/WorkspaceContainer.tsx new file mode 100644 index 0000000..c7e4a1b --- /dev/null +++ b/features/workspace/WorkspaceContainer.tsx @@ -0,0 +1,49 @@ +import React, { useEffect } from 'react'; +import { useAuth } from '../../hooks/useAuth'; +import { isStudioMode } from '../../services/apiUtils'; +import MainContainer from '../../containers/MainContainer'; +import WorkspaceView from './WorkspaceView'; + +/** + * WorkspaceContainer handles the 'Smart' logic: + * - Authentication state monitoring + * - Redirections + * - Data fetching/preparation for the View + */ +const WorkspaceContainer: React.FC = () => { + const { authState, user, workspaceUrl, signOutUrl } = useAuth(); + + useEffect(() => { + if (authState === 'unauthorized' && !isStudioMode()) { + window.location.href = '/home/login'; + } + }, [authState]); + + if (authState === 'checking') { + return ( +
+
+

Verifying access...

+
+ ); + } + + if (authState === 'unauthorized' && isStudioMode()) { + return ( +
+
+

Unauthorized

+

You do not have permission to access this application.

+
+
+ ); + } + + return ( + + + + ); +}; + +export default WorkspaceContainer; \ No newline at end of file diff --git a/features/workspace/WorkspaceView.tsx b/features/workspace/WorkspaceView.tsx new file mode 100644 index 0000000..b1bc4e0 --- /dev/null +++ b/features/workspace/WorkspaceView.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { User } from '../../types'; +import StatusGrid from './components/StatusGrid'; + +interface WorkspaceViewProps { + user: User | null; +} + +/** + * WorkspaceView is a 'Dumb' component: + * - It only receives data via props. + * - It defines the visual layout and style. + */ +const WorkspaceView: React.FC = ({ user }) => { + return ( +
+
+ + + +
+ +

+ Workspace Ready +

+ +

+ The HumanizeIQ shared framework is initialized. Your authentication context and AI connection layer are fully wired up. +

+ + +
+ ); +}; + +export default WorkspaceView; \ No newline at end of file diff --git a/features/workspace/components/StatusCard.tsx b/features/workspace/components/StatusCard.tsx new file mode 100644 index 0000000..9d3f709 --- /dev/null +++ b/features/workspace/components/StatusCard.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +interface StatusCardProps { + label: string; + value: string; + colorClass: string; +} + +const StatusCard: React.FC = ({ label, value, colorClass }) => { + return ( +
+

+ {label} +

+

+ {value} +

+
+ ); +}; + +export default StatusCard; \ No newline at end of file diff --git a/features/workspace/components/StatusGrid.tsx b/features/workspace/components/StatusGrid.tsx new file mode 100644 index 0000000..05bb844 --- /dev/null +++ b/features/workspace/components/StatusGrid.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { User } from '../../../types'; +import StatusCard from './StatusCard'; + +interface StatusGridProps { + user: User | null; +} + +const StatusGrid: React.FC = ({ user }) => { + return ( +
+ + + +
+ ); +}; + +export default StatusGrid; \ No newline at end of file diff --git a/firebase-applet-config.json b/firebase-applet-config.json new file mode 100644 index 0000000..e5498e2 --- /dev/null +++ b/firebase-applet-config.json @@ -0,0 +1,10 @@ +{ + "projectId": "gen-lang-client-0408711807", + "appId": "1:1099064516953:web:2f3d7fa475bd4194546c48", + "apiKey": "AIzaSyB28OqUKvw94yNEbkHhSSPn43qdPce-1t4", + "authDomain": "gen-lang-client-0408711807.firebaseapp.com", + "firestoreDatabaseId": "ai-studio-9f4101c2-8730-451b-9134-3b46f26726c6", + "storageBucket": "gen-lang-client-0408711807.firebasestorage.app", + "messagingSenderId": "1099064516953", + "measurementId": "" +} \ No newline at end of file diff --git a/firebase-blueprint.json b/firebase-blueprint.json new file mode 100644 index 0000000..5ec23b6 --- /dev/null +++ b/firebase-blueprint.json @@ -0,0 +1,73 @@ +{ + "entities": { + "User": { + "title": "User", + "description": "A user of the Aura platform.", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The user's full name." + }, + "email": { + "type": "string", + "format": "email", + "description": "The user's email address." + }, + "phone": { + "type": "string", + "description": "The user's phone number." + }, + "uid": { + "type": "string", + "description": "The user's unique Firebase Auth ID." + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "When the user account was created." + } + }, + "required": ["name", "email", "phone", "uid", "createdAt"] + }, + "Subscription": { + "title": "Subscription", + "description": "A user's subscription and credit settings.", + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The selected Aura plan." + }, + "topUpAmount": { + "type": "number", + "description": "The amount to top up." + }, + "rechargeLevel": { + "type": "number", + "description": "The balance level at which to auto-recharge." + }, + "uid": { + "type": "string", + "description": "The user's unique Firebase Auth ID." + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "When the subscription was last updated." + } + }, + "required": ["plan", "topUpAmount", "rechargeLevel", "uid", "updatedAt"] + } + }, + "firestore": { + "/users/{uid}": { + "schema": "User", + "description": "User profile data." + }, + "/subscriptions/{uid}": { + "schema": "Subscription", + "description": "User subscription data." + } + } +} diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..cd8d81a --- /dev/null +++ b/firestore.rules @@ -0,0 +1,89 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + // =============================================================== + // Assumed Data Model + // =============================================================== + // + // Collection: users + // Document ID: {uid} (Firebase Auth UID) + // Fields: + // - name: string (required, 1-100 chars) + // - email: string (required, valid email format) + // - phone: string (required, 1-20 chars) + // - uid: string (required, matches document ID) + // - createdAt: string (required, ISO 8601 format) + // + // Collection: subscriptions + // Document ID: {uid} (Firebase Auth UID) + // Fields: + // - plan: string (required, enum: ['Aura Pro']) + // - topUpAmount: number (required, positive) + // - rechargeLevel: number (required, positive) + // - uid: string (required, matches document ID) + // - updatedAt: string (required, ISO 8601 format) + // + // =============================================================== + + // =============================================================== + // Helper Functions + // =============================================================== + + function isAuthenticated() { + return request.auth != null; + } + + function isOwner(userId) { + return isAuthenticated() && request.auth.uid == userId; + } + + function isValidEmail(email) { + return email is string && email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); + } + + function isValidDateString(dateStr) { + return dateStr is string && dateStr.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.*Z?$"); + } + + function hasOnlyAllowedFields(fields) { + return request.resource.data.keys().hasOnly(fields); + } + + // Domain Validators + + function isValidUser(data) { + return hasOnlyAllowedFields(['name', 'email', 'phone', 'uid', 'createdAt']) && + data.name is string && data.name.size() > 0 && data.name.size() <= 100 && + isValidEmail(data.email) && + data.phone is string && data.phone.size() > 0 && data.phone.size() <= 20 && + data.uid == request.auth.uid && + isValidDateString(data.createdAt); + } + + function isValidSubscription(data) { + return hasOnlyAllowedFields(['plan', 'topUpAmount', 'rechargeLevel', 'uid', 'updatedAt']) && + data.plan in ['Aura Pro'] && + data.topUpAmount is number && data.topUpAmount > 0 && + data.rechargeLevel is number && data.rechargeLevel >= 0 && + data.uid == request.auth.uid && + isValidDateString(data.updatedAt); + } + + // =============================================================== + // Rules + // =============================================================== + + match /users/{uid} { + allow read, write: if true; + } + + match /subscriptions/{uid} { + allow read, write: if true; + } + + // Default deny + match /{path=**} { + allow read, write: if false; + } + } +} diff --git a/hooks/useAuth.ts b/hooks/useAuth.ts new file mode 100644 index 0000000..2512135 --- /dev/null +++ b/hooks/useAuth.ts @@ -0,0 +1,7 @@ + +import { useAuthContext } from '../contexts/AuthContext'; + +// Export as useAuth to maintain backward compatibility +export const useAuth = () => { + return useAuthContext(); +}; diff --git a/hooks/useAuthSession.ts b/hooks/useAuthSession.ts new file mode 100644 index 0000000..39c18fe --- /dev/null +++ b/hooks/useAuthSession.ts @@ -0,0 +1,62 @@ +import { useState, useEffect } from 'react'; +import { User, Role, Permission } from '../types'; +import * as appBuilderService from '../services/appBuilderService'; + +export const useAuthSession = () => { + const [authState, setAuthState] = useState<'checking' | 'authorized' | 'unauthorized'>('checking'); + const [user, setUser] = useState(null); + const [roles, setRoles] = useState([]); + const [permissions, setPermissions] = useState([]); + + useEffect(() => { + const checkAuth = async () => { + const isStudio = window.location.href.includes('.goog'); + const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; + + // Configure Gemini API Key globally for compatibility + (window as any).GEMINI_API_KEY = isStudio + ? ((window as any).process?.env?.API_KEY || 'NOTFOUND') + : 'NOT_SET'; + + if (isLocal) { + setUser({ name: 'Local Dev User', company_name: 'HumanizeIQ', uid: 'local-dev-uid' }); + setAuthState('authorized'); + setRoles(['Admin']); + setPermissions(['all:access']); + return; + } + + const authUrl = isGoogDomain ? 'https://www.playtest.humanizeiq.ai/auth/ai_studio' : '/auth/ai_studio'; + + try { + const fetchOptions: RequestInit = { credentials: 'include' }; + + const response = await fetch(authUrl, fetchOptions); + if (response.status === 200) { + const result = await response.json(); + if (result.data?.firstname) { + setUser({ + name: `${result.data.firstname} ${result.data.lastname}`, + company_name: result.data.company_name, + auth_cookie: result.data.auth_cookie_base64, + uid: result.data.uid + }); + setAuthState('authorized'); + const rbacData = await appBuilderService.getMyRbacDetails().catch(() => ({ roles: [], permissions: [] })); + setRoles(rbacData.roles); + setPermissions(rbacData.permissions); + } else { + setAuthState('unauthorized'); + } + } else { + setAuthState('unauthorized'); + } + } catch (error) { + setAuthState('unauthorized'); + } + }; + checkAuth(); + }, []); + + return { authState, user, roles, permissions }; +}; \ No newline at end of file diff --git a/hooks/useLocalStorage.ts b/hooks/useLocalStorage.ts new file mode 100644 index 0000000..cca7c4d --- /dev/null +++ b/hooks/useLocalStorage.ts @@ -0,0 +1,31 @@ +// FIX: Import React to make the 'React' namespace available for type annotations. +import React, { useState, useEffect } from 'react'; + +export function useLocalStorage(key: string, initialValue: T): [T, React.Dispatch>] { + const [storedValue, setStoredValue] = useState(() => { + if (typeof window === 'undefined') { + return initialValue; + } + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + console.error(error); + return initialValue; + } + }); + + useEffect(() => { + try { + const valueToStore = + typeof storedValue === 'function' + ? storedValue(storedValue) + : storedValue; + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + console.error(error); + } + }, [key, storedValue]); + + return [storedValue, setStoredValue]; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..282c624 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + Aura Central + + +
+ + + + diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..7f563ac --- /dev/null +++ b/index.tsx @@ -0,0 +1,22 @@ + +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import { ThemeProvider } from './contexts/ThemeContext'; +import { AuthProvider } from './contexts/AuthContext'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + + + + + +); diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..86264e4 --- /dev/null +++ b/instructions.md @@ -0,0 +1,43 @@ + +# 🚀 Getting Started with Aura Craft Studio + +This template is a pre-wired, modular framework designed for AI-accelerated development. Follow these two steps to build your application. + +## 1️⃣ Step 1: Manual Setup +Before using AI prompts, manually update `metadata.json` in the root directory. This tells the system who you are and what you are building. + +```json +{ + "name": "Your App Name", + "description": "Clear description of your app's purpose", + "organization": "YourOrg", + "project": "YourProject", + "component": "YourApp" +} +``` +*Note: The `organization`, `project`, and `component` fields are critical for the AI to fetch your specific system instructions and prompts from the CMS.* + +## 2️⃣ Step 2: AI-Driven Development +From this point forward, use the **Mandatory Prompt Template** below for every change or feature request. This ensures the AI respects the modular architecture and safety rules. + +### 📋 Mandatory Prompt Template +Copy and paste this into your prompt when asking for updates: + +> "I want to [describe your request]. +> +> **MANDATORY CONSTRAINTS:** +> 1. **STRICT ADHERENCE TO rules.md**: Read and follow all rules in `rules.md` without exception. +> 2. **CONTAINER/VIEW PATTERN**: Separate logic into smart `*Container.tsx` files and UI into presentational `*View.tsx` files. +> 3. **200-LINE LIMIT**: No single file may exceed 200 lines of code. Decompose into smaller modules or custom hooks if necessary. +> 4. **WIRING PROTECTION**: Do not modify core infrastructure files (Auth, API Utils, Gemini Service). +> 5. **AESTHETICS**: Use Tailwind CSS with full dark mode support (`dark:` variants) and responsive prefixes. +> 6. **MODULARITY**: Group new features under `features/[feature-name]/`." + +--- + +## 🛠️ Developer Reference (For the AI) +- **`useAuth()`**: Access user identity and RBAC permissions. +- **`useTheme()`**: Toggle between light and dark modes. +- **`generateResponse()`**: Call Gemini via the HumanizeIQ proxy without managing keys. +- **`apiService.ts`**: Standardized R2 cloud storage operations (Upload/Download/List). +- **`rules.md`**: The source of truth for all architectural constraints. diff --git a/local_cookie.json b/local_cookie.json new file mode 100644 index 0000000..7bad626 --- /dev/null +++ b/local_cookie.json @@ -0,0 +1,4 @@ +{ + "cookie": "VTJGc2RHVmtYMS9sVTJ5MGhJYmR0TFFOekQzRStwbnhRWmF0cDRDYndPYlZZQ1prcmpOWWtIWlBKNUx2a0lIQjAwWGczNlhFSktLV0lERjl2U0NXZDd5MG1XL0t3bTlmZmZseTN6N29tbTdmVGR2YWlRT2o3b2ZKM0RQZjEyWTVxMjJzNFBJdjlidjBLcGlhaUVWY3IrSFhzZHAwU3A3bGh6Sjlsdnd5VzlCN3BtRnVRWUhYSDUwUmt2NloreVBaQ0pnV2E4YmovT3hjRDdXM2JMNHlkeTd4QWZBbFE4OE9DaWcyREtIUHQ3aEpDeG4reGw0UG5IT3pmOG9ndFJoekViUHN0SGlLWUNhanRXSWxha0UwRVhwNllmSHBDOVlPdWEyWkk0YWJyY084djRjMDFJUWMwZXpkWEtNY05hamhqZjlkSXV5eWFjekRVMFdYYzZQZU53PT0=" +} + diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..7152163 --- /dev/null +++ b/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Aura Central", + "description": "A comprehensive dashboard for managing tasks, schedules, and AI-driven insights.", + "requestFramePermissions": [ + "microphone" + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ab1b9e3 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "react-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3000 --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@google/genai": "^1.29.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "dotenv": "^17.2.3", + "express": "^4.21.2", + "firebase": "^12.11.0", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", + "qrcode.react": "^4.2.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-markdown": "^10.1.0", + "vite": "^6.2.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^22.14.0", + "autoprefixer": "^10.4.21", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/public/version.json b/public/version.json new file mode 100644 index 0000000..b8fbae9 --- /dev/null +++ b/public/version.json @@ -0,0 +1 @@ +{"version":"1.0.0"} diff --git a/rules.md b/rules.md new file mode 100644 index 0000000..7c3d2dc --- /dev/null +++ b/rules.md @@ -0,0 +1,35 @@ +# 📜 HumanizeIQ AI Development Rules + +This document outlines the strict boundaries and guidelines for AI-driven modifications to this application. These rules ensure that the **"Wiring"** (Auth, API Proxies, RBAC) and **"Branding"** (Standard Layout) remain functional and consistent across all Aura Craft Studio projects. + +## 🚫 1. Immutable "Wiring" (DO NOT MODIFY) +The following files and logic sections are core infrastructure. Modification may break connectivity with the HumanizeIQ backend or Google AI Studio. + +- **`services/apiUtils.ts`**: Logic for switching between Studio and Deployed modes. +- **`services/geminiService.ts`**: The proxy-aware AI client initialization. +- **`services/apiService.ts`**: R2 storage integration methods. +- **`metadata.json`**: The application's DNA. +- **`local_cookie.json`**: Essential for authentication within Google AI Studio. + +## 🏗️ 2. Architecture: Container/View & SRP +To maintain scalability and the Single Responsibility Principle, follow these patterns: + +- **Logic vs. Display Separation**: Business logic, data fetching, and state orchestration must live in **Containers** (`*Container.tsx`) or **Hooks** (`use*.ts`). UI layout and styling must live in **Views** (`*View.tsx` or `*Layout.tsx`). +- **Max File Length**: No code file should exceed **200 lines**. If a file grows beyond this, it MUST be decomposed into smaller, more specific modules, sub-components, or custom hooks. +- **Modular Features**: New capabilities should be grouped under `features/[feature-name]/` with their own logic and display files. + +## ✅ 3. Permitted Modification Areas +- **`features/`**: Create domain-specific modules. +- **`components/`**: Create shared UI atoms or molecules. +- **`types.ts`**: Add new interfaces specific to the application's domain. +- **`hooks/`**: Add custom React hooks for local state or specific data fetching. + +## 🎨 4. Aesthetic & Theme Standards +- **Tailwind Strategy**: Always use Tailwind CSS classes. Avoid inline styles. +- **Dark Mode**: Every component **must** include `dark:` variants. +- **Responsiveness**: Ensure layouts are mobile-friendly using `sm:`, `md:`, and `lg:` prefixes. + +## 🤖 5. Integration Best Practices +- **Data Fetching**: Use `getUrlWithStudioAuth` and `getFetchOptions` from `apiUtils.ts`. +- **AI Prompts**: Use `getSystemInstruction(key)` from `geminiService.ts`. +- **Error Handling**: Implement loading states and graceful fallbacks. \ No newline at end of file diff --git a/services/apiService.ts b/services/apiService.ts new file mode 100644 index 0000000..ff3efe5 --- /dev/null +++ b/services/apiService.ts @@ -0,0 +1,160 @@ + +import { getUrlWithStudioAuth, getFetchOptions } from './apiUtils'; + +const getApiBaseUrl = (): string => { + const isStudioMode = window.location.href.includes('.goog'); + + if (isStudioMode) { + // In Studio Mode, use the absolute URL for the dev environment. + return 'https://www.playtest.humanizeiq.ai/api/r2-explorer'; + } + + // In deployed environments (dev, prod, local), use a relative URL. + return '/api/r2-explorer'; +}; + +export async function uploadFile(file: File, metadata: object): Promise { + const baseUrl = `${getApiBaseUrl()}/upload`; + const url = await getUrlWithStudioAuth(baseUrl); + + const formData = new FormData(); + formData.append('file', file); + formData.append('metadata', JSON.stringify(metadata)); + + const options = await getFetchOptions({ + method: 'POST', + body: formData, + }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + const error: any = new Error(`File upload failed with status ${response.status}: ${errorText}`); + try { + error.body = JSON.parse(errorText); + } catch (e) { + error.body = { error: errorText }; + } + throw error; + } + return await response.json(); + } catch (error) { + console.error('Error uploading file:', error); + throw error; + } +} + +export async function updateFile(file: File, fileIdOrPath: string, isPath: boolean = false, metadata?: object): Promise { + const baseUrl = `${getApiBaseUrl()}/update-file`; + const url = await getUrlWithStudioAuth(baseUrl); + + // Construct query params carefully to preserve existing auth params + const separator = url.includes('?') ? '&' : '?'; + const paramName = isPath ? 'path' : 'fileId'; + const fetchUrl = `${url}${separator}${paramName}=${encodeURIComponent(fileIdOrPath)}`; + + const formData = new FormData(); + formData.append('file', file); + if (metadata) { + formData.append('metadata', JSON.stringify(metadata)); + } + + const options = await getFetchOptions({ + method: 'PUT', + body: formData, + }); + + try { + const response = await fetch(fetchUrl, options); + if (!response.ok) { + const errorText = await response.text(); + const error: any = new Error(`File update failed with status ${response.status}: ${errorText}`); + try { + error.body = JSON.parse(errorText); + } catch (e) { + error.body = { error: errorText }; + } + throw error; + } + return await response.json(); + } catch (error) { + console.error('Error updating file:', error); + throw error; + } +} + +export async function listFiles(category?: string): Promise { + const baseUrl = `${getApiBaseUrl()}/files`; + let url = await getUrlWithStudioAuth(baseUrl); + + if (category) { + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}category=${encodeURIComponent(category)}`; + } + + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to list files: ${response.status} ${errorText}`); + } + return await response.json(); +} + +export async function listFilesByMetadata(metadata: any): Promise { + const baseUrl = `${getApiBaseUrl()}/files-by-metadata`; + const url = await getUrlWithStudioAuth(baseUrl); + + const options = await getFetchOptions({ + method: 'POST', + body: JSON.stringify({ metadata }) + }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to list files by metadata: ${response.status} ${errorText}`); + } + return await response.json(); + } catch (error) { + console.error('Error listing files by metadata:', error); + throw error; + } +} + +export async function downloadFile(pathOrId: string, isPath: boolean = true): Promise { + const baseUrl = `${getApiBaseUrl()}/download-file`; + let url = await getUrlWithStudioAuth(baseUrl); + + const separator = url.includes('?') ? '&' : '?'; + const param = isPath ? 'path' : 'fileId'; + url = `${url}${separator}${param}=${encodeURIComponent(pathOrId)}`; + + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to download file: ${response.status} ${errorText}`); + } + // Return JSON directly as we are storing JSON files + return await response.json(); +} + +export async function deleteFile(pathOrId: string, isPath: boolean = true): Promise { + const baseUrl = `${getApiBaseUrl()}/delete-file`; + let url = await getUrlWithStudioAuth(baseUrl); + + const separator = url.includes('?') ? '&' : '?'; + const param = isPath ? 'path' : 'fileId'; + url = `${url}${separator}${param}=${encodeURIComponent(pathOrId)}`; + + const options = await getFetchOptions({ method: 'DELETE' }); + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to delete file: ${response.status} ${errorText}`); + } + return await response.json(); +} diff --git a/services/apiUtils.ts b/services/apiUtils.ts new file mode 100644 index 0000000..27118ca --- /dev/null +++ b/services/apiUtils.ts @@ -0,0 +1,87 @@ + +/** + * Shared utility functions for making authenticated API calls. + * Handles logic for both Studio Mode and Deployed Mode. + */ + +export const isStudioMode = (): boolean => { + const hostname = window.location.href; + return hostname.includes('.goog'); +}; + +// --- Studio Mode Cookie Loading (Async) --- +// This promise ensures the cookie is fetched only once per session. +let studioCookiePromise: Promise | null = null; + +export const fetchStudioCookie = (): Promise => { + if (!isStudioMode()) { + return Promise.resolve(null); + } + if (studioCookiePromise) { + return studioCookiePromise; + } + studioCookiePromise = (async () => { + try { + // Fetch from the application's relative path + const response = await fetch('./local_cookie.json'); + if (!response.ok) { + console.error(`Failed to fetch local_cookie.json: ${response.statusText}`); + return null; + } + const data = await response.json(); + if (data && typeof data.cookie === 'string') { + return data.cookie; + } + console.error("Invalid format for local_cookie.json. Expected { \"cookie\": \"...\" }"); + return null; + } catch (error) { + console.error("Error fetching or parsing local_cookie.json:", error); + return null; + } + })(); + return studioCookiePromise; +}; +// --- End Studio Mode Cookie Loading --- + +/** + * Appends the Studio Mode authentication cookie as a query parameter to a URL if needed. + * @param baseUrl The base URL for the API call. + * @returns The URL with the auth parameter if in Studio Mode. + */ +export const getUrlWithStudioAuth = async (baseUrl: string): Promise => { + if (!isStudioMode()) { + return baseUrl; + } + const cookie = await fetchStudioCookie(); + if (!cookie) { + return baseUrl; + } + const param = `X-Studio-Cookie=${encodeURIComponent(cookie)}`; + if (baseUrl.includes('?')) { + return `${baseUrl}&${param}`; + } else { + return `${baseUrl}?${param}`; + } +} + +/** + * Creates the options object for a fetch call, including credentials and headers. + * @param options Initial RequestInit options. + * @returns A complete RequestInit object for the fetch call. + */ +export const getFetchOptions = async (options: RequestInit = {}): Promise => { + const headers = new Headers(options.headers); + + // For FormData, let the browser set the Content-Type with the correct boundary. + // For other POST/PUT/PATCH requests, default to application/json if not set. + if (!(options.body instanceof FormData) && options.method && ['POST', 'PUT', 'PATCH'].includes(options.method.toUpperCase()) && !headers.has('Content-Type')) { + headers.set('Content-Type', 'application/json'); + } + + // Studio auth is handled via query parameter, but 'credentials: include' is still needed for deployed mode cookies. + return { + ...options, + credentials: 'include', + headers: headers, + }; +}; diff --git a/services/appBuilder/componentService.ts b/services/appBuilder/componentService.ts new file mode 100644 index 0000000..01ce1b9 --- /dev/null +++ b/services/appBuilder/componentService.ts @@ -0,0 +1,122 @@ + +import type { ProjectComponent } from '../../types'; +import { isStudioMode, getUrlWithStudioAuth, getFetchOptions } from '../apiUtils'; +import { getAppBuilderApiBaseUrl } from './config'; + +// Helper to resolve the App Builder's own component based on metadata.json +let selfComponentPromise: Promise | null = null; + +// Re-implementing simplified getOrganizations/getProjects/getComponents locally to avoid circular dependencies or importing unused modules +// purely for getSelfComponent resolution logic if needed, or we can assume metadata is correct. +// However, the original implementation relied on fetching from DB to confirm. +// We will keep minimal fetch logic for getSelfComponent resolution. + +const fetchComponentsMinimal = async (projectId: number): Promise => { + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/components`; + const urlWithParams = new URL(baseUrl, window.location.origin); + urlWithParams.searchParams.append('projectId', projectId.toString()); + const finalUrl = baseUrl.startsWith('http') ? urlWithParams.href : `${urlWithParams.pathname}${urlWithParams.search}`; + const url = await getUrlWithStudioAuth(finalUrl); + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data.map((c: any) => ({...c, projectId: c.projectId || c.project_id})) : []; +}; + +const fetchOrganizationsMinimal = async (): Promise => { + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/organizations`; + const url = await getUrlWithStudioAuth(baseUrl); + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + return response.ok ? await response.json() : []; +}; + +const fetchProjectsMinimal = async (orgId: number): Promise => { + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/projects`; + const urlWithParams = new URL(baseUrl, window.location.origin); + urlWithParams.searchParams.append('organizationId', orgId.toString()); + const finalUrl = baseUrl.startsWith('http') ? urlWithParams.href : `${urlWithParams.pathname}${urlWithParams.search}`; + const url = await getUrlWithStudioAuth(finalUrl); + const options = await getFetchOptions({ method: 'GET' }); + const response = await fetch(url, options); + return response.ok ? await response.json() : []; +}; + +export const getSelfComponent = async (): Promise => { + if (selfComponentPromise) return selfComponentPromise; + + selfComponentPromise = (async () => { + try { + console.log("getSelfComponent: Starting lookup..."); + let url: string; + // Use document.baseURI to correctly locate metadata.json in both Studio and Deployed modes + if (isStudioMode()) { + const base = document.baseURI.endsWith('/') ? document.baseURI : `${document.baseURI}/`; + url = new URL('metadata.json', base).href; + } else { + // Calculate base: Host + first path segment (ignoring index.html) + const pathParts = window.location.pathname.split('/').filter(p => p && p !== 'index.html'); + const base = pathParts.length > 0 + ? `${window.location.origin}/${pathParts[0]}/` + : `${window.location.origin}/`; + url = new URL('metadata.json', base).href; + } + + const metaRes = await fetch(url); + + if (!metaRes.ok) { + console.warn(`getSelfComponent: Could not fetch metadata.json. Status: ${metaRes.status}`); + return null; + } + const metadata = await metaRes.json(); + console.log("getSelfComponent: Metadata loaded:", metadata); + + const { organization, project, component } = metadata; + + if (!organization || !project || !component) { + console.warn("getSelfComponent: metadata.json missing required fields (organization, project, component)"); + return null; + } + + // 1. Find Org + const orgs = await fetchOrganizationsMinimal(); + const orgObj = orgs.find(o => o.name === organization); + if (!orgObj) { + console.warn(`getSelfComponent: Organization '${organization}' not found in DB.`); + return null; + } + + // 2. Find Project + const projects = await fetchProjectsMinimal(orgObj.id); + const projObj = projects.find(p => p.name === project); + if (!projObj) { + console.warn(`getSelfComponent: Project '${project}' not found in org '${organization}'.`); + return null; + } + + // 3. Find Component + const components = await fetchComponentsMinimal(projObj.id); + // Match by name or title + const compObj = components.find(c => c.name === component || c.title === component); + if (!compObj) { + console.warn(`getSelfComponent: Component '${component}' not found in project '${project}'.`); + return null; + } + + console.log(`getSelfComponent: Resolved Self Component: ${compObj.id} for ${organization}/${project}/${component}`); + return compObj; + + } catch (e) { + console.error("getSelfComponent: Failed to resolve self component", e); + return null; + } + })(); + + return selfComponentPromise; +}; + +export const getSelfComponentId = async (): Promise => { + const comp = await getSelfComponent(); + return comp ? comp.id : null; +}; diff --git a/services/appBuilder/config.ts b/services/appBuilder/config.ts new file mode 100644 index 0000000..fb18de0 --- /dev/null +++ b/services/appBuilder/config.ts @@ -0,0 +1,21 @@ + +export const getAppBuilderApiBaseUrl = (): string => { + const isStudioMode = window.location.href.includes('.goog'); + if (isStudioMode) { + // Management APIs (RBAC, Auth) in the HumanizeIQ ecosystem usually sit under this segment + return 'https://www.playtest.humanizeiq.ai/api/ai_studio_manager_api'; + } else { + // In deployed mode, use a relative path. + return '/api/ai_studio_manager_api'; + } +}; +export const getUserManagementApiBaseUrl = (): string => { + const isStudioMode = window.location.href.includes('.goog'); + if (isStudioMode) { + // Management APIs (RBAC, Auth) in the HumanizeIQ ecosystem usually sit under this segment + return 'https://www.playtest.humanizeiq.ai/api/user_management'; + } else { + // In deployed mode, use a relative path. + return '/api/user_management'; + } +}; \ No newline at end of file diff --git a/services/appBuilder/contentService.ts b/services/appBuilder/contentService.ts new file mode 100644 index 0000000..7c645e4 --- /dev/null +++ b/services/appBuilder/contentService.ts @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/services/appBuilder/promptService.ts b/services/appBuilder/promptService.ts new file mode 100644 index 0000000..e17aa2c --- /dev/null +++ b/services/appBuilder/promptService.ts @@ -0,0 +1,84 @@ + +import type { ComponentPrompt } from '../../types'; +import { getUrlWithStudioAuth, getFetchOptions } from '../apiUtils'; +import { getAppBuilderApiBaseUrl } from './config'; +import { getSelfComponentId } from './componentService'; + +/** + * Retrieves prompts for a specific component. + * @param componentId The ID of the component. + */ +export const getComponentPrompts = async (componentId: number): Promise => { + console.log(`API: Fetching prompts for componentId ${componentId}`); + const baseUrl = `${getAppBuilderApiBaseUrl()}/app-builder/component-prompts`; + + const urlWithParams = new URL(baseUrl, window.location.origin); + urlWithParams.searchParams.append('componentId', componentId.toString()); + const finalUrl = baseUrl.startsWith('http') ? urlWithParams.href : `${urlWithParams.pathname}${urlWithParams.search}`; + + const url = await getUrlWithStudioAuth(finalUrl); + const options = await getFetchOptions({ method: 'GET' }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + const error: any = new Error(`API call failed with status ${response.status}: ${errorText}`); + error.status = response.status; + try { error.body = JSON.parse(errorText); } catch (e) { error.body = errorText; } + throw error; + } + return await response.json(); + } catch (error: any) { + console.error(`Error fetching prompts for component ${componentId}:`, error); + throw error; + } +}; + +/** + * Helper to convert prompt array to record map + */ +const convertPromptsToMap = (prompts: ComponentPrompt[]): Record => { + const map: Record = {}; + if (Array.isArray(prompts)) { + prompts.forEach(p => { + if (p && p.title) { + map[p.title] = p.content; + } + }); + } + return map; +}; + +/** + * Retrieves prompts for a specific component as a key-value map. + * This is useful for easy lookup by prompt title. + */ +export const getPrompts = async (componentId: number): Promise> => { + try { + const prompts = await getComponentPrompts(componentId); + return convertPromptsToMap(prompts); + } catch (e) { + console.error(`Failed to get prompts for component ${componentId}`, e); + return {}; + } +} + +/** + * Retrieves system prompts for the current application (Self Component). + * Returns a map of prompt title to prompt content. + */ +export const getSystemPrompts = async (): Promise> => { + try { + const selfId = await getSelfComponentId(); + if (!selfId) { + console.warn("getSystemPrompts: No self component ID found."); + return {}; + } + const prompts = await getComponentPrompts(selfId); + return convertPromptsToMap(prompts); + } catch (e) { + console.error("Failed to get system prompts", e); + return {}; + } +} diff --git a/services/appBuilder/rbacService.ts b/services/appBuilder/rbacService.ts new file mode 100644 index 0000000..952fbaa --- /dev/null +++ b/services/appBuilder/rbacService.ts @@ -0,0 +1,31 @@ + +import type { Role, Permission } from '../../types'; +import { getUrlWithStudioAuth, getFetchOptions } from '../apiUtils'; +import { getAppBuilderApiBaseUrl } from './config'; + +/** + * Retrieves the current user's roles and permissions from the RBAC endpoint. + * @returns A promise that resolves with an object containing arrays of roles and permissions. + */ +export const getMyRbacDetails = async (): Promise<{ roles: Role[], permissions: Permission[] }> => { + console.log('API: Fetching RBAC details (roles and permissions)'); + const baseUrl = `${getAppBuilderApiBaseUrl()}/rbac/me`; + const url = await getUrlWithStudioAuth(baseUrl); + const options = await getFetchOptions({ method: 'GET' }); + + try { + const response = await fetch(url, options); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to fetch RBAC details: ${response.status} ${errorText}`); + } + const data = await response.json(); + return { + roles: Array.isArray(data.roles) ? data.roles : [], + permissions: Array.isArray(data.permissions) ? data.permissions : [] + }; + } catch (error: any) { + console.error('Error fetching user RBAC details:', error); + throw error; + } +}; diff --git a/services/appBuilderService.ts b/services/appBuilderService.ts new file mode 100644 index 0000000..bca7408 --- /dev/null +++ b/services/appBuilderService.ts @@ -0,0 +1,8 @@ + +// This file re-exports all functionalities from the modular services. +// This maintains backward compatibility with existing imports. + +export * from './appBuilder/config'; +export * from './appBuilder/componentService'; +export * from './appBuilder/promptService'; +export * from './appBuilder/rbacService'; diff --git a/services/geminiService.ts b/services/geminiService.ts new file mode 100644 index 0000000..41794d2 --- /dev/null +++ b/services/geminiService.ts @@ -0,0 +1,54 @@ + +import { GoogleGenAI } from "@google/genai"; +import { getSystemPrompts } from './appBuilderService'; +import { fetchStudioCookie } from './apiUtils' + +/** + * Returns a configured GoogleGenAI instance. + * Per @google/genai guidelines, it strictly uses process.env.API_KEY. + */ +export const getAi = async () => { + // The API key must be obtained exclusively from process.env.API_KEY + const apiKey = process.env.API_KEY || 'NOT_FOUND'; + + const href = window.location.href; + const hostname = window.location.hostname; + const isStudioMode = href.includes('.goog'); + const isLocal = hostname === 'localhost' || hostname === '127.0.0.1'; + + /** + * HumanizeIQ specific: Non-studio modes (deployed apps) route through a proxy + * for unified access control. + */ + let baseUrl='https://www.playtest.humanizeiq.ai/api-proxy' + if (!isStudioMode) { + baseUrl = `${window.location.origin}/api-proxy`; + } + const headers: Record = { + 'User-Agent': 'DraftingStudio' + }; + if (isStudioMode) { + const cookie = await fetchStudioCookie(); + if (cookie) { + headers['X-Studio-Cookie'] = cookie; + } + } + + return new GoogleGenAI({ + apiKey: apiKey, + httpOptions: { + baseUrl: baseUrl, + headers: headers + } + }); +}; + +// Cache for system prompts to avoid repeated fetches +let systemPromptsCache: Record | null = null; + +export const getSystemInstruction = async (key: string): Promise => { + if (!systemPromptsCache) { + systemPromptsCache = await getSystemPrompts(); + } + return systemPromptsCache[key] || ''; +}; diff --git a/services/llmService.ts b/services/llmService.ts new file mode 100644 index 0000000..aeff2bb --- /dev/null +++ b/services/llmService.ts @@ -0,0 +1,89 @@ + + +import { getAi } from './geminiService'; +import { isStudioMode, fetchStudioCookie } from './apiUtils'; +import OpenAI from 'openai'; +import type { ChatMessage, ModelProvider, LLMConfig } from '../types'; + +export const MODELS: Record = { + openai: ['chatgpt-latest'], + // Use the latest recommended model for basic text tasks + google: ['gemini-3-flash-preview'] +}; + +export const generateResponse = async ( + config: LLMConfig, + messages: ChatMessage[] +): Promise => { + // Prepare headers for authentication (needed for proxy in Studio Mode) + const headers: Record = {}; + if (isStudioMode()) { + const cookie = await fetchStudioCookie(); + if (cookie) { + headers['X-Studio-Cookie'] = cookie; + } + } + + if (config.provider === 'google') { + const ai = await getAi(); + const history = messages.slice(0, -1).map(m => ({ + role: m.role, + parts: [{ text: m.context || m.content }] + })); + + const lastMessage = messages[messages.length - 1]; + const content = lastMessage.context || lastMessage.content; + + const chatConfig: any = { + model: config.model, + history: history, + }; + + if (config.systemInstruction) { + chatConfig.config = { + systemInstruction: config.systemInstruction + }; + } + + const chat = ai.chats.create(chatConfig); + + const response = await chat.sendMessage({ message: content }); + // Correct usage of .text property + return response.text || "No response text."; + + } else if (config.provider === 'openai') { + // API Key handled by proxy + const apiKey = config.apiKey || 'managed-by-proxy'; + + const baseURL = 'https://www.playtest.humanizeiq.ai/api-proxy/openai/v1'; + + const openai = new OpenAI({ + apiKey: apiKey, + baseURL: baseURL, + dangerouslyAllowBrowser: true, + defaultHeaders: headers + }); + + let openAiMessages = messages.map(m => ({ + role: m.role === 'model' ? 'assistant' : 'user', + content: m.context || m.content + })) as any[]; + + if (config.systemInstruction) { + openAiMessages = [ + { role: 'system', content: config.systemInstruction }, + ...openAiMessages + ]; + } + + const completion = await openai.chat.completions.create({ + messages: openAiMessages, + model: config.model, + }); + + return completion.choices[0]?.message?.content || "No response text."; + + } + + throw new Error(`Unsupported provider: ${config.provider}`); +}; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..9462d10 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,1239 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { + CheckCircle2, + Clock, + Calendar, + Bell, + MoreHorizontal, + Plus, + Smartphone, + MessageSquare, + Mic, + Menu, + Smile, + Send, + FileText, + Image as ImageIcon, + ChevronRight, + Layout, + LayoutGrid, + Mail, + Settings, + Folder, + Briefcase, + User, + Zap, + Timer, + Target, + Moon, + Sun, + ShieldCheck, + Coins, + BarChart3, + Lock, + Shield, + Eye, + Fingerprint, + File, + Brain, + LogOut, + RefreshCw, + ChevronDown, + Square, + Play, + Pause, + X, + Video, + Copy +} from 'lucide-react'; +import { motion, AnimatePresence } from 'motion/react'; +import ReactMarkdown from 'react-markdown'; +import { GoogleGenAI } from "@google/genai"; +import { AuthFlow } from './components/AuthFlow'; +import { SubscriptionFlow } from './components/SubscriptionFlow'; +import { db, doc, getDoc } from './firebase'; + +// --- Types --- +interface Task { + id: string; + title: string; + category: 'Urgent' | 'Today' | 'Upcoming' | 'Waiting On'; + completed: boolean; + icon?: React.ReactNode; + type: 'Personal' | 'Professional'; +} + +interface ChatMessage { + role: 'user' | 'model'; + text: string; +} + +// --- Mock Data --- +const INITIAL_TASKS: Task[] = [ + { id: '1', title: 'Prepare for 2 PM meeting', category: 'Urgent', completed: false, icon: , type: 'Professional' }, + { id: '2', title: "Reply to Sarah's email", category: 'Urgent', completed: false, icon: , type: 'Professional' }, + { id: '3', title: 'Finish report for project X', category: 'Today', completed: false, icon: , type: 'Professional' }, + { id: '4', title: 'Call mom this evening', category: 'Today', completed: false, icon: , type: 'Personal' }, + { id: '5', title: 'Client presentation tomorrow', category: 'Upcoming', completed: false, icon: , type: 'Professional' }, + { id: '6', title: 'Book doctor appointment', category: 'Upcoming', completed: false, icon: , type: 'Personal' }, + { id: '7', title: 'Approval from John', category: 'Waiting On', completed: false, icon: , type: 'Professional' }, + { id: '8', title: 'Reply from Alex', category: 'Waiting On', completed: false, icon: , type: 'Personal' }, +]; + +const SCHEDULE = [ + { time: '2:00 PM', title: 'Project Meeting', icon: , type: 'Professional' }, + { time: '4:30 PM', title: 'Gym Session', icon: , type: 'Personal' }, + { time: '7:00 PM', title: 'Dinner with Kate', icon: , type: 'Personal' }, +]; + +const RECENT_FILES = [ + { name: 'Project X Report', type: 'doc', icon: , viewType: 'Professional' }, + { name: 'Budget Plan', type: 'sheet', icon: , viewType: 'Professional' }, + { name: 'Family Photo.jpg', type: 'image', icon: , viewType: 'Personal' }, +]; + +const RECENT_CHATS = [ + { name: 'Sarah', lastMsg: 'Need your feedback', icon: }, + { name: 'John', lastMsg: 'Sent the document', icon: }, +]; + +const NOTIFICATIONS = [ + { id: '1', title: 'New message from Sarah', time: '2m ago', icon: }, + { id: '2', title: 'Meeting starts in 15m', time: '15m ago', icon: }, + { id: '3', title: 'Project X report updated', time: '1h ago', icon: }, +]; + +// --- Components --- + +interface TaskItemProps { + task: Task; + onClick?: (task: Task) => void; +} + +const TaskItem: React.FC = ({ task, onClick }) => ( +
onClick?.(task)}> +
{task.icon || }
+ {task.title} + +
+); + +interface SectionProps { + title: string; + tasks: Task[]; + colorClass: string; + onTaskClick?: (task: Task) => void; +} + +const Section: React.FC = ({ title, tasks, colorClass, onTaskClick }) => ( +
+

{title}

+
+ {tasks.map(t => )} +
+
+); + +const ToolbarItem = ({ icon, label, active = false, hideLabel = false, onClick }: { icon: React.ReactNode, label: string, active?: boolean, hideLabel?: boolean, onClick?: () => void }) => ( +
+ {icon} + {!hideLabel && {label}} +
+); + +export default function App() { + const [user, setUser] = useState<{ name: string; email: string; phone: string; uid: string } | null>(null); + const [subscription, setSubscription] = useState<{ plan: string; topUpAmount: number; rechargeLevel: number; uid: string } | null>(null); + const [isAuthReady, setIsAuthReady] = useState(false); + const [expandedTile, setExpandedTile] = useState<'projects' | 'schedule' | 'files' | 'people' | 'billing' | 'health' | 'notifications' | null>(null); + const [isNotificationsExpanded, setIsNotificationsExpanded] = useState(false); + const [viewMode, setViewMode] = useState<'Personal' | 'Professional'>('Professional'); + const [isProfileDropdownOpen, setIsProfileDropdownOpen] = useState(false); + const [auraMode, setAuraMode] = useState<'chat' | 'voice' | 'meet'>('chat'); + const [isRecording, setIsRecording] = useState(false); + const [recordingTime, setRecordingTime] = useState(0); + const [theme, setTheme] = useState<'light' | 'dark'>(() => { + if (typeof window !== 'undefined') { + return (localStorage.getItem('aura_theme') as 'light' | 'dark') || 'light'; + } + return 'light'; + }); + const [tasks] = useState(INITIAL_TASKS); + const [chatInput, setChatInput] = useState(''); + const [messages, setMessages] = useState([ + { role: 'model', text: `Hi ${user?.name.split(' ')[0] || 'Alex'}, Welcome to Aura Central — AI for Understanding, Reasoning, and Action. Here's your update for today:` } + ]); + + useEffect(() => { + let interval: any; + if (isRecording) { + interval = setInterval(() => { + setRecordingTime((prev) => prev + 1); + }, 1000); + } else { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [isRecording]); + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + }; + + useEffect(() => { + const root = window.document.documentElement; + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + localStorage.setItem('aura_theme', theme); + }, [theme]); + + const toggleTheme = () => { + setTheme(prev => prev === 'light' ? 'dark' : 'light'); + }; + + useEffect(() => { + const loadSavedData = async () => { + const savedUid = localStorage.getItem('aura_uid'); + if (savedUid) { + try { + // Fetch user data + const userDoc = await getDoc(doc(db, 'users', savedUid)); + if (userDoc.exists()) { + setUser(userDoc.data() as any); + } + // Fetch subscription data + const subDoc = await getDoc(doc(db, 'subscriptions', savedUid)); + if (subDoc.exists()) { + setSubscription(subDoc.data() as any); + } + } catch (error) { + console.error('Error loading saved data:', error); + } + } + setIsAuthReady(true); + }; + loadSavedData(); + }, []); + + useEffect(() => { + if (user) { + setMessages([{ role: 'model', text: `Hi ${user.name.split(' ')[0]}, Here's your update for today:` }]); + } + }, [user]); + const [isTyping, setIsTyping] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isAppsMenuOpen, setIsAppsMenuOpen] = useState(false); + const chatEndRef = useRef(null); + const auraColRef = useRef(null); + const actionColRef = useRef(null); + const notificationsColRef = useRef(null); + const infoColRef = useRef(null); + const usageColRef = useRef(null); + + const scrollToCol = (ref: React.RefObject) => { + ref.current?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); + setIsMenuOpen(false); + }; + + const scrollToBottom = () => { + chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const handleSendMessage = async () => { + if (!chatInput.trim()) return; + + const userMsg = chatInput; + setMessages(prev => [...prev, { role: 'user', text: userMsg }]); + setChatInput(''); + setIsTyping(true); + + try { + const apiKey = process.env.GEMINI_API_KEY; + if (!apiKey) { + throw new Error("GEMINI_API_KEY is not defined"); + } + const ai = new GoogleGenAI({ apiKey }); + const response = await ai.models.generateContent({ + model: "gemini-3-flash-preview", + contents: userMsg, + config: { + systemInstruction: "You are Ask Aura, a helpful personal AI assistant in the Aura Central dashboard. Keep responses concise and professional. You are talking to Alex." + } + }); + + setMessages(prev => [...prev, { role: 'model', text: response.text || "I'm sorry, I couldn't process that." }]); + } catch (error) { + console.error("Gemini Error:", error); + setMessages(prev => [...prev, { role: 'model', text: "Sorry, I'm having trouble connecting right now. Please check your API key." }]); + } finally { + setIsTyping(false); + } + }; + + const handleActionClick = async (item: any, type: 'task' | 'event' | 'file' | 'notification' | 'project') => { + let userPrompt = ""; + let contextData = ""; + + if (type === 'task') { + userPrompt = `I'm looking at the task: "${item.title}". Can you help me with this?`; + contextData = `Task Details: Title: ${item.title}, Category: ${item.category}, Status: ${item.completed ? 'Completed' : 'Pending'}, Type: ${item.type}`; + } else if (type === 'event') { + userPrompt = `I'm looking at my schedule: "${item.title}" at ${item.time}. What should I prepare?`; + contextData = `Event Details: Title: ${item.title}, Time: ${item.time}, Type: ${item.type}`; + } else if (type === 'file') { + userPrompt = `I'm looking at the file: "${item.name}". Can you summarize it or tell me more about it?`; + contextData = `File Details: Name: ${item.name}, Type: ${item.type}, View Context: ${item.viewType}`; + } else if (type === 'notification') { + userPrompt = `I just saw this notification: "${item.title}". What's the context?`; + contextData = `Notification Details: Title: ${item.title}, Time: ${item.time}`; + } else if (type === 'project') { + userPrompt = `I'm looking at the project: "${item.name}". It's at ${item.progress}% progress. What are the next steps?`; + contextData = `Project Details: Name: ${item.name}, Progress: ${item.progress}%`; + } + + setMessages(prev => [...prev, { role: 'user', text: userPrompt }]); + scrollToCol(auraColRef); + setIsTyping(true); + + try { + const apiKey = process.env.GEMINI_API_KEY; + if (!apiKey) throw new Error("GEMINI_API_KEY is not defined"); + + const ai = new GoogleGenAI({ apiKey }); + const response = await ai.models.generateContent({ + model: "gemini-3-flash-preview", + contents: `Context Information: ${contextData}\n\nUser Question: ${userPrompt}`, + config: { + systemInstruction: `You are Ask Aura, a helpful personal AI assistant. The user just clicked on an item in their dashboard, setting it as the current context. Use the provided Context Information to give a helpful, concise, and professional response. You are talking to Alex.` + } + }); + + setMessages(prev => [...prev, { role: 'model', text: response.text || "I'm sorry, I couldn't process that context." }]); + } catch (error) { + console.error("Gemini Error:", error); + setMessages(prev => [...prev, { role: 'model', text: "I'm having trouble analyzing that context right now." }]); + } finally { + setIsTyping(false); + } + }; + + if (!isAuthReady) { + return ( +
+ +
+ ); + } + + if (!user) { + return setUser(data)} />; + } + + if (!subscription) { + return setSubscription(data)} />; + } + + return ( +
+ {/* Header */} +
+ {/* Left: Logo & Title */} +
+
+ +
+
+
+

Aura Central

+
+ + +
+
+

+ AI for Understanding, Reasoning, and Action +

+
+
+ + {/* Center: Toolbar */} +
+ } label="Chat" active /> + } label="Explorer" /> + } label="Mail" /> + } label="Calendar" /> + } label="Settings" /> +
+ } label="All" /> +
+ + {/* Right: Profile Dropdown & Theme Toggle */} +
+ {/* Mobile Apps Button */} +
+ + + + {isAppsMenuOpen && ( + +
Select App
+ + + + + +
+ +
+ )} +
+
+ + {/* Profile Dropdown */} +
+ + + + {isProfileDropdownOpen && ( + + + +
+ +
+ )} +
+
+ + +
+
+ + {/* Main Grid */} +
+ + {/* Middle Column: Ask Aura - First in JSX for mobile swipeability */} +
+
+ +
+
+ + + + + {isMenuOpen && ( + + + + + + + + )} + +
+

Ask Aura

+
+ + + +
+
+ + + {auraMode === 'chat' ? ( + + +
+ + + + + +
+ +
+ + {messages.map((msg, i) => ( + +
+
+ {msg.text} +
+
+ {msg.role === 'model' && ( +
+ + +
+ )} +
+ ))} + {isTyping && ( + +
+
+
+
+
+
+
+
+ )} +
+
+
+ +
+ setChatInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()} + className="aura-input pr-24 text-sm" + /> +
+ + +
+
+ + ) : auraMode === 'voice' ? ( + +
+
+
+ +
+
+ {isRecording && ( +
+ + +
+ )} +
+ +

Aura Voice

+

+ {isRecording ? 'Aura is listening...' : 'Tap to start a voice conversation with Aura.'} +

+ + +
+ ) : ( + +
+
+ {isRecording ? ( + + + + ) : ( +
+
+ )} +
+ {isRecording && ( +
+ +
+ )} +
+ +

+ {isRecording ? 'Recording Meeting...' : 'Aura Meet'} +

+

+ {isRecording ? 'Aura is listening and transcribing your meeting in real-time.' : 'Start recording to have Aura transcribe, summarize, and extract action items from your meeting.'} +

+ +
+ {formatTime(recordingTime)} +
+ +
+ + + +
+ + {isRecording && ( + +
+
+ Live Transcript +
+

+ "So the main goal for Q2 is to increase user retention by 15% through the new loyalty program..." +

+
+ )} +
+ )} + +
+ + {/* Left Column Group: Actionables & Projects */} +
+ {/* Card 1: Actionables */} +
+
+
+ + + + {isMenuOpen && ( + + + + + + + + )} + +
+

+ {viewMode === 'Professional' ? 'Actionables' : 'Personal Hub'} +

+
+
+ +
+
+
t.category === 'Urgent' && t.type === viewMode)} + colorClass="aura-text-accent-red" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
t.category === 'Today' && t.type === viewMode)} + colorClass="aura-text-accent-blue" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
t.category === 'Upcoming' && t.type === viewMode)} + colorClass="aura-text-accent-emerald" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
t.category === 'Waiting On' && t.type === viewMode)} + colorClass="aura-text-accent-amber" + onTaskClick={(t) => handleActionClick(t, 'task')} + /> +
+
+
+
+ + {/* Right Column Group: Information & Usage Stats */} +
+ {/* Card 4: Information */} +
+
+
+ + + + {isMenuOpen && ( + + + + + + + )} + +
+
+ +
+ + {!expandedTile ? ( + + {/* Notifications Tile - Spans 2 columns */} + +
+
+ + Notifications +
+ +
+
+ {NOTIFICATIONS.slice(0, 2).map((n) => ( + handleActionClick(n, 'notification')} + className="flex items-center gap-3 p-2 rounded-xl bg-aura-bg/50 border border-aura-border/50 cursor-pointer hover:bg-aura-bg transition-colors" + > +
+ {n.icon} +
+
+

{n.title}

+

{n.time}

+
+
+ ))} +
+
+ {/* Projects Cube */} + setExpandedTile('projects')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 03 + Projects +
+ + {/* Schedule Cube */} + setExpandedTile('schedule')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 2 PM + Schedule +
+ + {/* File Cube */} + setExpandedTile('files')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 12 + Files +
+ + {/* People Cube */} + setExpandedTile('people')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 05 + People +
+ + {/* Billing Cube */} + setExpandedTile('billing')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ $42 + Billing +
+ + {/* Health Cube */} + setExpandedTile('health')} + className="aspect-square p-4 rounded-3xl bg-aura-surface border border-aura-border flex flex-col items-center justify-center text-center group transition-all cursor-pointer" + > +
+ +
+ 98% + Health +
+
+ ) : ( + +
+ + {expandedTile} +
+ +
+ {expandedTile === 'notifications' && ( +
+ {NOTIFICATIONS.map((n) => ( + handleActionClick(n, 'notification')} + className="aura-row-card p-3 group hover:bg-aura-bg transition-colors cursor-pointer" + > +
+ {n.icon} +
+
+

{n.title}

+

{n.time}

+
+
+ ))} +
+ )} + + {expandedTile === 'projects' && ( +
+ {[ + { name: 'Project X', progress: 65, color: 'aura-bg-accent-blue' }, + { name: 'Client Campaign', progress: 40, color: 'aura-bg-accent-indigo' }, + { name: 'Budget Revision', progress: 85, color: 'aura-bg-accent-emerald' } + ].map((p, i) => ( +
handleActionClick(p, 'project')}> +
+ {p.name} + {p.progress}% +
+
+ +
+
+ ))} +
+ )} + + {expandedTile === 'schedule' && ( +
+ {SCHEDULE.filter(item => item.type === viewMode).map((item, i) => ( +
handleActionClick(item, 'event')}> +
{item.time}
+
+ {item.icon} + {item.title} +
+
+ ))} +
+ )} + + {expandedTile === 'files' && ( +
+ {RECENT_FILES.filter(file => file.viewType === viewMode).map((file, i) => ( +
handleActionClick(file, 'file')}> +
+ {file.icon} +
+ {file.name} + +
+ ))} +
+ )} + + {expandedTile === 'people' && ( +
+ {RECENT_CHATS.map((chat, i) => ( +
+
+ {chat.icon} +
+
+ {chat.name} + {chat.lastMsg} +
+
+
+ ))} +
+ )} + + {expandedTile === 'billing' && ( +
+
+
+ Plan + {subscription?.plan} +
+
+
+
+
+
+
+ Credits + 100% +
+
+ Storage + 42% +
+
+
+ )} + + {expandedTile === 'health' && ( +
+
+
+
+ +
+
+

System Health

+

All systems operational

+
+
+ 98% +
+
+ )} +
+
+ )} +
+
+
+
+
+
+ ); +} diff --git a/src/components/AuthFlow.tsx b/src/components/AuthFlow.tsx new file mode 100644 index 0000000..f9a6595 --- /dev/null +++ b/src/components/AuthFlow.tsx @@ -0,0 +1,1129 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import { + User, + Mail, + Phone, + AlertCircle, + ShieldCheck, + ArrowRight, + ChevronLeft, + ChevronRight, + CheckCircle2, + Smartphone, + Lock, + Zap, + Brain, + Target, + Calendar, + MessageSquare, + FileText, + Shield, + Coins, + Mic, + Globe, + Bell, + RefreshCw, + KeyRound, + LogIn, + BookOpen, + Heart, + GraduationCap, + Users, + Library, + LayoutGrid, + X +} from 'lucide-react'; +import { QRCodeSVG } from 'qrcode.react'; +import { auth, db, doc, setDoc, signInWithGoogle, OperationType, handleFirestoreError } from '../firebase'; +import { PrivacyPolicy } from './PrivacyPolicy'; + +const BENEFITS = [ + { + title: "Privacy & Security", + description: "Enterprise-grade encryption and advanced threat detection.", + icon: , + color: "blue" + }, + { + title: "Peak Productivity", + description: "Streamline your workflow with intelligent automation.", + icon: , + color: "orange" + }, + { + title: "Autonomous Assistant", + description: "Works across WhatsApp, Web, and Voice, always synchronized.", + icon: , + color: "blue" + }, + { + title: "Agent Library", + description: "Growing collection of specialized agents for any task.", + icon: , + color: "indigo" + }, + { + title: "Social Impact", + description: "Your usage funds scholarships for students in need.", + icon: , + color: "rose" + } +]; + +type AuthStep = + | 'WELCOME' + | 'PRIVACY_POLICY' + | 'COLLECT_INVITE_CODE' + | 'COLLECT_EMAIL' + | 'COLLECT_NICKNAME' + | 'COLLECT_FULL_NAME' + | 'COLLECT_PHONE' + | 'VERIFY_EMAIL' + | 'VERIFY_PHONE' + | 'SETUP_TOTP' + | 'VERIFY_TOTP' + | 'SUCCESS'; + +interface AuthFlowProps { + onComplete: (userData: { name: string; email: string; phone: string; uid: string }) => void; +} + +export const AuthFlow: React.FC = ({ onComplete }) => { + const [step, setStep] = useState('WELCOME'); + const [showMobileBenefits, setShowMobileBenefits] = useState(false); + const [showPrivacyPolicy, setShowPrivacyPolicy] = useState(false); + const [mobileCarouselIndex, setMobileCarouselIndex] = useState(0); + const scrollContainerRef = useRef(null); + const isAutoScrolling = useRef(false); + const [currentBenefitIndex, setCurrentBenefitIndex] = useState(0); + const [inviteCode, setInviteCode] = useState(''); + const [nickname, setNickname] = useState(''); + const [fullName, setFullName] = useState(''); + const [email, setEmail] = useState(''); + const [phone, setPhone] = useState(''); + const [uid, setUid] = useState(() => { + const saved = localStorage.getItem('aura_uid'); + if (saved) return saved; + const newId = 'user_' + Math.random().toString(36).substr(2, 9); + localStorage.setItem('aura_uid', newId); + return newId; + }); + const [emailOtp, setEmailOtp] = useState(''); + const [phoneOtp, setPhoneOtp] = useState(''); + const [totpCode, setTotpCode] = useState(''); + const [isProcessing, setIsProcessing] = useState(false); + const [error, setError] = useState(null); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, [step]); + + useEffect(() => { + if (step === 'WELCOME') { + const interval = setInterval(() => { + setCurrentBenefitIndex((prev) => (prev + 1) % BENEFITS.length); + }, 3000); + return () => clearInterval(interval); + } + }, [step]); + + useEffect(() => { + if (showMobileBenefits) { + const totalItems = BENEFITS.length + 1; // +1 for Enterprise card + const interval = setInterval(() => { + setMobileCarouselIndex((prev) => (prev + 1) % totalItems); + }, 5000); + return () => clearInterval(interval); + } + }, [showMobileBenefits]); + + useEffect(() => { + if (scrollContainerRef.current) { + const container = scrollContainerRef.current; + const targetScroll = mobileCarouselIndex * container.clientWidth; + + if (Math.abs(container.scrollLeft - targetScroll) > 5) { + isAutoScrolling.current = true; + container.scrollTo({ + left: targetScroll, + behavior: 'smooth' + }); + + const timeout = setTimeout(() => { + isAutoScrolling.current = false; + }, 600); + return () => clearTimeout(timeout); + } + } + }, [mobileCarouselIndex]); + + const saveUserToFirestore = async () => { + const userData = { + name: fullName || nickname, + nickname, + fullName, + email, + phone, + uid, + createdAt: new Date().toISOString() + }; + try { + await setDoc(doc(db, 'users', uid), userData); + return userData; + } catch (error) { + console.error('Firestore Error (Simulated Auth):', error); + return userData; // Fallback for demo if rules block + } + }; + + const handleNext = async (e?: React.FormEvent) => { + if (e) e.preventDefault(); + setIsProcessing(true); + + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 800)); + setIsProcessing(false); + + switch (step) { + case 'WELCOME': + setError(null); + setStep('COLLECT_INVITE_CODE'); + break; + case 'COLLECT_INVITE_CODE': + if (inviteCode) { + if (inviteCode.toUpperCase() === 'AURA-2026') { + setStep('COLLECT_EMAIL'); + setError(null); + } else { + setError('Invalid invite code. Access denied.'); + setStep('WELCOME'); + } + } + break; + case 'COLLECT_EMAIL': + if (email) setStep('COLLECT_NICKNAME'); + break; + case 'COLLECT_NICKNAME': if (nickname) setStep('COLLECT_FULL_NAME'); break; + case 'COLLECT_FULL_NAME': if (fullName) setStep('COLLECT_PHONE'); break; + case 'COLLECT_PHONE': if (phone) setStep('VERIFY_EMAIL'); break; + case 'VERIFY_EMAIL': if (emailOtp === '123456') setStep('VERIFY_PHONE'); else alert('Use 123456 for demo'); break; + case 'VERIFY_PHONE': if (phoneOtp === '123456') setStep('SETUP_TOTP'); else alert('Use 123456 for demo'); break; + case 'SETUP_TOTP': setStep('VERIFY_TOTP'); break; + case 'VERIFY_TOTP': + if (totpCode === '123456') { + setIsProcessing(true); + const userData = await saveUserToFirestore(); + setIsProcessing(false); + setStep('SUCCESS'); + } else { + alert('Use 123456 for demo'); + } + break; + case 'SUCCESS': onComplete({ name: fullName || nickname, email, phone, uid }); break; + } + }; + + const renderStep = () => { + switch (step) { + case 'WELCOME': + return ( + +
+ {/* Left Side: Hero */} +
+ {/* Background Decorative Element */} +
+
+ +
+
+
+ +
+
+

+ Aura Central +

+ + AI for Understanding, Reasoning, and Action. + +
+
+ +
+
+

+ AI for Understanding, Reasoning, and Action. + Your Lifestyle Guide in an AI world +

+
+ + {error && ( + + + {error} + + )} + + {/* Benefits Preview Card - Mobile Only */} + +
+
+ +
+ + {/* HumanizeIQ Branding */} +
+
+ Humanize
IQ
Logo +
+
+

A Product From

+

HumanizeIQ

+

Profit for Good (PFG) Company

+
+
+
+ + {/* Bottom Stats/Trust */} +
+
+

99.9%

+

Uptime

+
+
+

256-bit

+

Security

+
+
+

Global

+

Access

+
+
+
+ + {/* Right Side: Benefit Card Cubes */} +
+
+
+

Platform Benefits

+ +
+ {/* Combined Benefit: Privacy & Security */} +
+
+
+ +
+
+ +
+
+

Privacy & Security

+

+ Enterprise-grade encryption and advanced threat detection. Your data remains yours. +

+ +
+ + {/* Benefit Cube 3 */} +
+
+ +
+

Peak Productivity

+

+ Streamline your workflow with intelligent automation for mundane tasks. +

+
+ + {/* Autonomous Assistant Cube */} +
+
+
+ +
+

Autonomous Assistant

+
+

+ Works across WhatsApp, Web, and Voice, always synchronized. +

+
+ + + +
+
+ + {/* Agent Library */} +
+
+ +
+

Agent Library

+

+ Growing collection of specialized agents: Presentation, Tutor, Research, and more. +

+
+ + {/* Combined Benefit: Social Impact & Community - 2 Slots */} +
+
+
+ +
+
+ +
+
+
+
The Aura Difference
+

Why Choose Aura?

+

+ By using Aura, you support students receiving paid training to survive in an AI world. + Our profits fund scholarships and stipends for those in need. +

+
+
+
+
+
+ + {/* Enterprise Level Plan - Aligned to Bottom */} +
+
+
+
+
Standard for All
+

Enterprise Level Plan

+

Premium features unlocked for every consumer.

+
+
    +
  • Bundled Usage
  • +
  • Pay-as-you-go
  • +
  • Never Expire
  • +
  • Profit-Sharing
  • +
+
+ +
+
+ $7.99 + /mo +
+
+ Rewards system based on company profits. +
+ +
+
+
+
+ + {/* Mobile Benefits Overlay */} + + {showMobileBenefits && ( + + {/* Content - Full screen horizontal cards */} +
{ + if (isAutoScrolling.current) return; + const container = e.currentTarget; + if (container.clientWidth === 0) return; + const index = Math.round(container.scrollLeft / container.clientWidth); + if (index !== mobileCarouselIndex) { + setMobileCarouselIndex(index); + } + }} + className="flex-grow overflow-x-auto snap-x snap-mandatory no-scrollbar flex h-full" + > + {BENEFITS.map((benefit, idx) => ( +
+
+ {/* Close Button inside card */} +
+ +
+ + {/* Decorative Background for Card */} +
+
+ +
+ {React.cloneElement(benefit.icon as React.ReactElement<{ className?: string }>, { className: "w-12 h-12 text-aura-accent-blue" })} +
+ +

{benefit.title}

+

+ {benefit.description} +

+ +
+ {benefit.title === "Privacy & Security" && ( + + )} + + {benefit.title === "Social Impact" && ( +
+

PFG Initiative

+

Your usage directly funds scholarships for students in the AI era.

+
+ )} + +
+
+
+ ))} + + {/* Mobile Enterprise Plan Card */} +
+
+ {/* Close Button inside card */} +
+ +
+ +
+
Standard for All
+

Enterprise Access

+

Premium features unlocked for every consumer.

+
+ +
    +
  • Bundled Usage
  • +
  • Pay-as-you-go
  • +
  • Never Expire
  • +
  • Profit-Sharing
  • +
+ +
+
+ $7.99 + /mo +
+
+
+
+
+ + {/* Final CTA in Overlay removed to focus on information */} + + {/* Navigation Arrows - Positioned at 2/3 marker */} +
+ +
+ +
+ +
+ + {/* Navigation Dots at Bottom */} +
+
+ {Array.from({ length: BENEFITS.length + 1 }).map((_, i) => ( +
+ ))} +
+
+ + )} + +
+
+ ); + + case 'PRIVACY_POLICY': + return null; // Handled as overlay + + case 'COLLECT_INVITE_CODE': + return ( + +
+ +
+ Invite Only +

Enter your invite code

+
+
+
+

AI for Understanding, Reasoning, and Action

+

Aura is currently in private beta. Please enter your access code to begin.

+
+ +
+ + setInviteCode(e.target.value)} + className="auth-input-field uppercase tracking-widest" + required + /> + + +
+ ); + + case 'COLLECT_EMAIL': + return ( + +
+ +
+ Step 01 +

What's your email?

+
+
+
+

We'll validate your invite code with this email address.

+
+
+ + setEmail(e.target.value)} + className="auth-input-field" + required + /> + + +

Demo: Use AURA-2026

+
+ ); + + case 'COLLECT_NICKNAME': + return ( + +
+ +
+ Step 02 +

What should we call you?

+
+
+
+

This is how Aura will address you.

+
+
+ + setNickname(e.target.value)} + className="auth-input-field" + required + /> + + +
+ ); + + case 'COLLECT_FULL_NAME': + return ( + +
+ +
+ Step 03 +

And your full name?

+
+
+
+

For your official profile and documents.

+
+
+ + setFullName(e.target.value)} + className="auth-input-field" + required + /> + + +
+ ); + + case 'COLLECT_PHONE': + return ( + +
+ +
+ Step 04 +

And your phone number?

+
+
+
+

We'll use this for secure verification.

+
+
+ + setPhone(e.target.value)} + className="auth-input-field" + required + /> + + +
+ ); + + case 'VERIFY_EMAIL': + return ( + +
+ +
+ Verification +

Check your email

+
+
+
+

We sent a 6-digit code to {email}

+
+
+
+ + setEmailOtp(e.target.value)} + className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono" + required + /> +
+

Demo: Use 123456

+ +
+
+ ); + + case 'VERIFY_PHONE': + return ( + +
+ +
+ Verification +

One more code

+
+
+
+

We sent a text to {phone}

+
+
+
+ + setPhoneOtp(e.target.value)} + className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono" + required + /> +
+

Demo: Use 123456

+ +
+
+ ); + + case 'SETUP_TOTP': + return ( + +
+ +
+ Security +

Setup 2FA

+
+
+
+

Scan this QR code with Google Authenticator or Authy.

+
+ +
+ +
+ +
+

Manual Secret

+ JBSWY3DPEHPK3PXP +
+ + +
+ ); + + case 'VERIFY_TOTP': + return ( + +
+ +
+ Final Step +

Verify App Code

+
+
+
+

Enter the 6-digit code from your authenticator app.

+
+
+
+ + setTotpCode(e.target.value)} + className="aura-input py-5 pl-14 pr-6 text-3xl tracking-[0.5em] font-mono" + required + /> +
+

Demo: Use 123456

+ +
+
+ ); + + case 'SUCCESS': + return ( + +
+ +
+
+

You're all set!

+

Welcome to the future of productivity, {nickname || fullName.split(' ')[0]}.

+
+ +
+ ); + } + }; + + return ( +
+ {/* Background Accents */} +
+
+
+
+ +
+ + {renderStep()} + + + {/* Privacy Policy Overlay */} + + {showPrivacyPolicy && ( + setShowPrivacyPolicy(false)} /> + )} + +
+ + {/* Progress Indicator */} + {step !== 'WELCOME' && step !== 'SUCCESS' && ( +
+ {['COLLECT_INVITE_CODE', 'COLLECT_EMAIL', 'COLLECT_NICKNAME', 'COLLECT_FULL_NAME', 'COLLECT_PHONE', 'VERIFY_EMAIL', 'VERIFY_PHONE', 'SETUP_TOTP', 'VERIFY_TOTP'].map((s, i) => ( +
+ ))} +
+ )} +
+ ); +}; diff --git a/src/components/PrivacyPolicy.tsx b/src/components/PrivacyPolicy.tsx new file mode 100644 index 0000000..e5a371c --- /dev/null +++ b/src/components/PrivacyPolicy.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Shield, X, ArrowLeft } from 'lucide-react'; +import { motion } from 'motion/react'; + +interface PrivacyPolicyProps { + onBack: () => void; +} + +export const PrivacyPolicy: React.FC = ({ onBack }) => { + return ( + + {/* Header with Close Button */} +
+
+ + Privacy Policy +
+ +
+ +
+
+
+
+ +
+
+

Privacy Policy

+

Last Updated: March 23, 2026

+
+
+ +
+
+

1. Introduction

+

+ At Aura Central, we take your privacy seriously. This Privacy Policy explains how we collect, use, and protect your personal information when you use our AI-powered platform. +

+
+ +
+

2. Data Collection

+

+ We collect information you provide directly to us, such as your name, email address, and any data you input into our AI tools. We also collect technical data like your IP address and device information to improve our services. +

+
+ +
+

3. Data Usage

+

+ Your data is used solely to provide and improve the Aura Central experience. We do not sell your personal data to third parties. Your interactions with our AI are used to provide personalized responses and are protected by enterprise-grade encryption. +

+
+ +
+

4. Security

+

+ We implement industry-standard security measures to protect your data, including 256-bit encryption and secure data centers. Access to your data is strictly controlled and monitored. +

+
+ +
+

5. Your Rights

+

+ You have the right to access, correct, or delete your personal information at any time. If you have any questions about your data, please contact our privacy team. +

+
+
+ +
+

+ By using Aura Central, you agree to the terms of this Privacy Policy. If you do not agree, please do not use the platform. +

+
+
+
+
+ ); +}; diff --git a/src/components/SubscriptionFlow.tsx b/src/components/SubscriptionFlow.tsx new file mode 100644 index 0000000..ed35b13 --- /dev/null +++ b/src/components/SubscriptionFlow.tsx @@ -0,0 +1,202 @@ +import React, { useState } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import { + Zap, + Check, + CreditCard, + ArrowRight, + ShieldCheck, + TrendingUp, + RefreshCw, + Coins +} from 'lucide-react'; +import { db, doc, setDoc, OperationType, handleFirestoreError } from '../firebase'; + +interface SubscriptionData { + plan: string; + topUpAmount: number; + rechargeLevel: number; + uid: string; +} + +interface SubscriptionFlowProps { + uid: string; + onComplete: (data: SubscriptionData) => void; +} + +export const SubscriptionFlow: React.FC = ({ uid, onComplete }) => { + const [topUpAmount, setTopUpAmount] = useState(50); + const [rechargeLevel, setRechargeLevel] = useState(10); + const [isProcessing, setIsProcessing] = useState(false); + + const handleComplete = async () => { + setIsProcessing(true); + const subData = { + plan: 'Aura Central Platform', + topUpAmount, + rechargeLevel, + uid, + updatedAt: new Date().toISOString() + }; + try { + await setDoc(doc(db, 'subscriptions', uid), subData); + setIsProcessing(false); + onComplete(subData); + } catch (error) { + handleFirestoreError(error, OperationType.CREATE, `subscriptions/${uid}`); + setIsProcessing(false); + } + }; + + return ( +
+ {/* Background Accents */} +
+
+
+
+ + + {/* Left: Plan Details */} +
+
+
+ +
+

Choose your Aura

+

Power your productivity with intelligent credits.

+
+ +
+
+ +
+
+
+
+

Aura Central Platform

+

Monthly Access Fee

+
+
+ $29 + /mo +
+
+
+ {[ + 'Unified Communication Hub', + 'Omnichannel AI Assistant', + 'WhatsApp, Web, & Voice', + 'Priority Security & Privacy' + ].map((feature, i) => ( +
+ + {feature} +
+ ))} +
+
+
+ +
+ +
+

Secure Billing

+

Encrypted by industry-standard protocols.

+
+
+
+ + {/* Right: Usage Configuration */} +
+
+
+
+
+ +
+

Usage Configuration

+
+

Set up your pay-as-you-go usage credits. You only pay for what you consume.

+ +
+ +
+ {[25, 50, 100].map((amount) => ( + + ))} +
+
+
+ +
+
+
+ +
+

Auto-Recharge

+
+ +
+
+ + ${rechargeLevel} +
+ setRechargeLevel(parseInt(e.target.value))} + className="w-full h-2 bg-slate-200 dark:bg-slate-800 rounded-lg appearance-none cursor-pointer accent-indigo-600" + /> +

+ We'll automatically top up your account with ${topUpAmount} when your balance falls below ${rechargeLevel}. +

+
+
+
+ +
+
+ Total Due Today + ${29 + topUpAmount} +
+ +
+
+
+
+ ); +}; diff --git a/src/firebase.ts b/src/firebase.ts new file mode 100644 index 0000000..fe28a04 --- /dev/null +++ b/src/firebase.ts @@ -0,0 +1,88 @@ +import { initializeApp } from 'firebase/app'; +import { getAuth, GoogleAuthProvider, signInWithPopup, onAuthStateChanged, User as FirebaseUser } from 'firebase/auth'; +import { getFirestore, doc, setDoc, getDoc, onSnapshot, getDocFromServer } from 'firebase/firestore'; +import firebaseConfig from '../firebase-applet-config.json'; + +// Initialize Firebase SDK +const app = initializeApp(firebaseConfig); +export const db = getFirestore(app, firebaseConfig.firestoreDatabaseId); +export const auth = getAuth(app); +export const googleProvider = new GoogleAuthProvider(); + +// Auth helpers +export const signInWithGoogle = async () => { + try { + const result = await signInWithPopup(auth, googleProvider); + return result.user; + } catch (error) { + console.error('Error signing in with Google:', error); + throw error; + } +}; + +// Firestore Error Handling +export enum OperationType { + CREATE = 'create', + UPDATE = 'update', + DELETE = 'delete', + LIST = 'list', + GET = 'get', + WRITE = 'write', +} + +export interface FirestoreErrorInfo { + error: string; + operationType: OperationType; + path: string | null; + authInfo: { + userId: string | undefined; + email: string | null | undefined; + emailVerified: boolean | undefined; + isAnonymous: boolean | undefined; + tenantId: string | null | undefined; + providerInfo: { + providerId: string; + displayName: string | null; + email: string | null; + photoUrl: string | null; + }[]; + } +} + +export function handleFirestoreError(error: unknown, operationType: OperationType, path: string | null) { + const errInfo: FirestoreErrorInfo = { + error: error instanceof Error ? error.message : String(error), + authInfo: { + userId: auth.currentUser?.uid, + email: auth.currentUser?.email, + emailVerified: auth.currentUser?.emailVerified, + isAnonymous: auth.currentUser?.isAnonymous, + tenantId: auth.currentUser?.tenantId, + providerInfo: auth.currentUser?.providerData.map(provider => ({ + providerId: provider.providerId, + displayName: provider.displayName, + email: provider.email, + photoUrl: provider.photoURL + })) || [] + }, + operationType, + path + } + console.error('Firestore Error: ', JSON.stringify(errInfo)); + throw new Error(JSON.stringify(errInfo)); +} + +// Connection test +async function testConnection() { + try { + await getDocFromServer(doc(db, 'test', 'connection')); + } catch (error) { + if(error instanceof Error && error.message.includes('the client is offline')) { + console.error("Please check your Firebase configuration. "); + } + } +} +testConnection(); + +export { doc, setDoc, getDoc, onSnapshot }; +export type { FirebaseUser }; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..0f7896f --- /dev/null +++ b/src/index.css @@ -0,0 +1,29 @@ +@import "tailwindcss"; + +@utility no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +} + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); + +/* Theme & Variables */ +@import "./styles/theme.css"; + +/* Base Styles & Scrollbars */ +@import "./styles/base.css"; + +/* Reusable UI Components */ +@import "./styles/components.css"; + +/* Layout Styles */ +@import "./styles/layout.css"; + +/* Assistant Specific Styles */ +@import "./styles/aura.css"; + +/* Authentication Flow Styles */ +@import "./styles/auth.css"; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..080dac3 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/src/styles/aura.css b/src/styles/aura.css new file mode 100644 index 0000000..96e9c10 --- /dev/null +++ b/src/styles/aura.css @@ -0,0 +1,67 @@ +@layer components { + .aura-chat-bubble-user { + @apply max-w-[85%] p-4 rounded-[1rem] text-sm text-white rounded-tr-none shadow-sm; + background: linear-gradient(135deg, var(--aura-primary), var(--aura-accent-indigo)); + } + + .aura-chat-bubble-model { + @apply max-w-[85%] p-4 rounded-[1rem] text-sm rounded-tl-none border border-aura-border shadow-sm; + background-color: var(--aura-surface); + color: var(--aura-text); + } + + .aura-message-actions { + @apply flex items-center gap-1 mt-2 transition-opacity duration-200; + } + + @media (min-width: 1024px) { + .aura-message-container:hover .aura-message-actions { + @apply opacity-100; + } + .aura-message-actions { + @apply opacity-0; + } + } + + @media (max-width: 1023px) { + .aura-message-actions { + @apply opacity-100; + } + } + + .aura-suggestion-pill-blue { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-blue-50 dark:bg-blue-900/20 border-blue-100 dark:border-blue-800/50 hover:bg-blue-100 dark:hover:bg-blue-900/30; + color: var(--aura-accent-blue); + } + + .aura-suggestion-pill-orange { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-orange-50 dark:bg-orange-900/20 border-orange-100 dark:border-orange-800/50 hover:bg-orange-100 dark:hover:bg-orange-900/30; + color: var(--aura-accent-orange); + } + + .aura-suggestion-pill-indigo { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-indigo-50 dark:bg-indigo-900/20 border-indigo-100 dark:border-indigo-800/50 hover:bg-indigo-100 dark:hover:bg-indigo-900/30; + color: var(--aura-accent-indigo); + } + + .aura-suggestion-pill-red { + @apply flex items-center gap-2 px-3 py-1.5 rounded-full border text-xs font-medium whitespace-nowrap transition-colors bg-red-50 dark:bg-red-900/20 border-red-100 dark:border-red-800/50 hover:bg-red-100 dark:hover:bg-red-900/30; + color: var(--aura-accent-red); + } + + .aura-voice-circle { + @apply w-40 h-40 rounded-full flex items-center justify-center transition-all duration-500; + } + + .aura-voice-core { + @apply w-24 h-24 rounded-full flex items-center justify-center shadow-xl transition-all duration-300; + } + + .aura-meet-circle { + @apply w-32 h-32 rounded-full flex items-center justify-center transition-all duration-500; + } + + .aura-meet-core { + @apply w-16 h-16 rounded-full flex items-center justify-center shadow-lg transition-all; + } +} diff --git a/src/styles/auth.css b/src/styles/auth.css new file mode 100644 index 0000000..c9e0649 --- /dev/null +++ b/src/styles/auth.css @@ -0,0 +1,45 @@ +@layer components { + .auth-container { + @apply min-h-screen flex items-center justify-center p-6 bg-aura-bg relative overflow-hidden; + } + + .auth-card { + @apply aura-glass-card p-12 w-full max-w-xl flex flex-col items-center text-center relative z-10; + } + + .auth-step-container { + @apply space-y-8 w-full max-w-md; + } + + .auth-input-container { + @apply relative; + } + + .auth-input-icon { + @apply absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 w-6 h-6; + } + + .auth-input-field { + @apply aura-input py-5 pl-14 pr-6 text-xl; + } + + .auth-submit-btn { + @apply absolute right-3 top-1/2 -translate-y-1/2 bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-900 p-3 rounded-xl hover:bg-blue-600 dark:hover:bg-blue-400 disabled:opacity-50 transition-all; + } + + .auth-progress-indicator { + @apply fixed bottom-12 left-1/2 -translate-x-1/2 flex gap-2; + } + + .auth-progress-dot { + @apply h-1.5 rounded-full transition-all duration-500; + } + + .auth-progress-dot-active { + @apply w-8 bg-blue-600; + } + + .auth-progress-dot-inactive { + @apply w-2 bg-slate-200 dark:bg-slate-800; + } +} diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 0000000..65af4df --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,49 @@ +@layer base { + body { + @apply font-sans antialiased transition-colors duration-300; + background: radial-gradient(circle at 50% 0%, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + color: var(--aura-text); + } + + /* Custom scrollbar */ + ::-webkit-scrollbar { + width: 6px; + } + ::-webkit-scrollbar-track { + background: transparent; + } + ::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 10px; + } + .dark ::-webkit-scrollbar-thumb { + background: #334155; + } + ::-webkit-scrollbar-thumb:hover { + background: #94a3b8; + } + .dark ::-webkit-scrollbar-thumb:hover { + background: #475569; + } + + .no-scrollbar::-webkit-scrollbar { + display: none; + } + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + } + + .markdown-body p { + @apply mb-2 last:mb-0; + } + .markdown-body ul { + @apply list-disc list-inside mb-2; + } + .markdown-body ol { + @apply list-decimal list-inside mb-2; + } + .markdown-body strong { + @apply font-bold; + } +} diff --git a/src/styles/components.css b/src/styles/components.css new file mode 100644 index 0000000..bddf477 --- /dev/null +++ b/src/styles/components.css @@ -0,0 +1,108 @@ +@utility aura-card { + @apply backdrop-blur-md border rounded-xl transition-all duration-300 shadow-sm; + background-color: color-mix(in srgb, var(--aura-surface), transparent 5%); + border-color: var(--aura-border); +} + +@utility aura-input { + @apply w-full rounded-xl py-3 px-4 transition-all outline-none border-2; + background-color: var(--aura-surface); + border-color: var(--aura-border); + color: var(--aura-text); +} + +@utility aura-glass-card { + @apply backdrop-blur-xl border rounded-3xl shadow-2xl; + background-color: color-mix(in srgb, var(--aura-surface), transparent 20%); + border-color: var(--aura-border); +} + +@layer components { + .aura-card-hover { + @apply hover:shadow-md hover:border-blue-500/30 dark:hover:border-blue-400/30; + } + + .aura-btn-primary { + @apply px-6 py-3 rounded-xl font-bold transition-all flex items-center justify-center gap-2 shadow-lg; + background-color: var(--aura-text); + color: var(--aura-surface); + } + + .aura-btn-primary:hover { + @apply opacity-90 scale-[1.02]; + } + + .aura-btn-secondary { + @apply px-4 py-2 rounded-xl font-semibold transition-all flex items-center justify-center gap-2 border; + background-color: var(--aura-surface); + border-color: var(--aura-border); + color: var(--aura-text); + } + + .aura-btn-secondary:hover { + @apply bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600; + } + + .aura-input:focus { + @apply border-blue-500/50 ring-4 ring-blue-500/10; + } + + .aura-badge { + @apply px-2 py-0.5 rounded-md text-[10px] font-bold uppercase tracking-wider; + } + + .aura-logo { + @apply w-10 h-10 rounded-xl flex items-center justify-center shadow-lg; + background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%); + box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.2); + } + + .aura-toggle-group { + @apply flex p-1 rounded-2xl border shadow-inner; + background-color: color-mix(in srgb, var(--aura-bg), var(--aura-text) 5%); + border-color: var(--aura-border); + } + + .aura-gradient-card { + @apply rounded-3xl p-6 text-white shadow-2xl relative overflow-hidden; + background: linear-gradient(135deg, #2563eb 0%, #4f46e5 100%); + box-shadow: 0 20px 25px -5px rgba(37, 99, 235, 0.3); + } + + .aura-progress-track { + @apply h-1 w-full rounded-full overflow-hidden; + background-color: var(--aura-border); + } + + .aura-avatar-container { + @apply w-24 h-24 rounded-full overflow-hidden border-4 shadow-lg; + border-color: var(--aura-border); + } + + .aura-row-card { + @apply flex items-center gap-3 p-2 rounded-xl transition-colors; + background-color: color-mix(in srgb, var(--aura-bg), var(--aura-text) 2%); + } + + .aura-row-card:hover { + @apply bg-slate-100 dark:bg-slate-800; + } + + .aura-gradient-top-bar { + @apply absolute top-0 left-0 w-full h-1; + background: linear-gradient(to right, #60a5fa, #6366f1, #a855f7); + } + + .section-title { + @apply text-[10px] font-bold uppercase tracking-widest mb-3; + color: var(--aura-text-muted); + } + + .item-row { + @apply flex items-center gap-3 p-2 rounded-lg transition-colors cursor-pointer; + } + + .item-row:hover { + @apply bg-slate-100/50 dark:bg-slate-800/50; + } +} diff --git a/src/styles/layout.css b/src/styles/layout.css new file mode 100644 index 0000000..50dc826 --- /dev/null +++ b/src/styles/layout.css @@ -0,0 +1,47 @@ +@layer components { + .aura-dashboard { + @apply min-h-screen p-4 bg-aura-bg transition-colors duration-300 w-full; + } + + .aura-header { + @apply flex items-center justify-between mb-6 px-4 gap-6; + } + + .aura-main-grid { + @apply flex overflow-x-auto snap-x snap-mandatory xl:grid xl:grid-cols-12 gap-4 mb-4 h-[calc(100vh-120px)] min-h-[600px] xl:min-h-[750px] no-scrollbar; + } + + .aura-col-action { + @apply w-full flex-shrink-0 snap-center xl:w-auto xl:flex-shrink xl:snap-none xl:col-span-2 aura-card p-4 flex flex-col h-full overflow-hidden; + } + + .aura-col-aura { + @apply w-full flex-shrink-0 snap-center xl:w-auto xl:flex-shrink xl:snap-none xl:col-span-7 aura-card p-5 flex flex-col h-full relative overflow-hidden; + } + + .aura-col-info { + @apply w-full flex-shrink-0 snap-center xl:w-auto xl:flex-shrink xl:snap-none xl:col-span-3 aura-card p-4 flex flex-col h-full overflow-hidden; + } + + .aura-bottom-grid { + @apply grid grid-cols-1 xl:grid-cols-3 gap-4; + } + + .aura-toolbar { + @apply flex items-center gap-1 backdrop-blur-sm p-1.5 rounded-2xl border shadow-sm; + background-color: color-mix(in srgb, var(--aura-surface), transparent 50%); + border-color: color-mix(in srgb, var(--aura-border), transparent 50%); + } + + .aura-toolbar-item { + @apply flex items-center gap-2 rounded-xl cursor-pointer transition-all; + } + + .aura-toolbar-item-active { + @apply bg-aura-surface shadow-sm text-aura-accent-blue; + } + + .aura-toolbar-item-inactive { + @apply text-aura-text-muted hover:bg-aura-surface/50 hover:text-aura-text; + } +} diff --git a/src/styles/theme.css b/src/styles/theme.css new file mode 100644 index 0000000..ce74443 --- /dev/null +++ b/src/styles/theme.css @@ -0,0 +1,62 @@ +@theme { + --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace; + + --color-aura-bg: var(--aura-bg); + --color-aura-surface: var(--aura-surface); + --color-aura-primary: var(--aura-primary); + --color-aura-text: var(--aura-text); + --color-aura-text-muted: var(--aura-text-muted); + --color-aura-border: var(--aura-border); + --color-aura-accent: var(--aura-accent); + --color-aura-accent-blue: var(--aura-accent-blue); + --color-aura-accent-emerald: var(--aura-accent-emerald); + --color-aura-accent-red: var(--aura-accent-red); + --color-aura-accent-amber: var(--aura-accent-amber); + --color-aura-accent-indigo: var(--aura-accent-indigo); + --color-aura-accent-purple: var(--aura-accent-purple); + --color-aura-accent-orange: var(--aura-accent-orange); +} + +:root { + --aura-bg: #f1f5f9; + --aura-surface: #ffffff; + --aura-primary: #2563eb; + --aura-primary-hover: #1d4ed8; + --aura-text: #0f172a; + --aura-text-muted: #475569; + --aura-border: #cbd5e1; + --aura-accent: #2563eb; + --aura-accent-blue: #2563eb; + --aura-accent-emerald: #059669; + --aura-accent-red: #dc2626; + --aura-accent-amber: #d97706; + --aura-accent-indigo: #4f46e5; + --aura-accent-purple: #9333ea; + --aura-accent-orange: #ea580c; + + --bg-gradient-start: #e2e8f0; + --bg-gradient-end: #f1f5f9; +} + +.dark { + --aura-bg: #020617; + --aura-surface: #0f172a; + --aura-primary: #059669; + --aura-primary-hover: #047857; + --aura-text: #f8fafc; + --aura-text-muted: #94a3b8; + --aura-border: #1e293b; + --aura-accent: #059669; + --aura-accent-blue: #60a5fa; + --aura-accent-emerald: #059669; + --aura-accent-red: #f87171; + --aura-accent-amber: #fbbf24; + --aura-accent-indigo: #818cf8; + --aura-accent-purple: #c084fc; + --aura-accent-orange: #fb923c; + + --bg-gradient-start: #0f172a; + --bg-gradient-end: #020617; + color-scheme: dark; +} diff --git a/src/styles/utilities.css b/src/styles/utilities.css new file mode 100644 index 0000000..6018195 --- /dev/null +++ b/src/styles/utilities.css @@ -0,0 +1,28 @@ +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 10px; +} +.dark ::-webkit-scrollbar-thumb { + background: #334155; +} +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} +.dark ::-webkit-scrollbar-thumb:hover { + background: #475569; +} + +.no-scrollbar::-webkit-scrollbar { + display: none; +} +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d88f175 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..0fa6cf7 --- /dev/null +++ b/types.ts @@ -0,0 +1,173 @@ + + +export interface ChatMessage { + role: 'user' | 'model'; + content: string; + context?: string; // Stores full content (e.g., with file attachments) for AI context, while content is for display +} + +export type LogStatus = 'pending' | 'success' | 'error' | 'info'; + +export interface LogMessage { + id: number; + message: string; + status: LogStatus; +} + +export interface User { + name: string; + company_name?: string; + auth_cookie?: string; + uid?: string; +} + +export interface UserSettings { + systemPrompt: string; +} + +export type Role = string; + +export type Permission = string; + +export interface AppInfo { + id: number; + name: string; + createdBy: string; + primary_domain?: string; + base_url?: string; + studio_app_url?: string; +} + +export interface PullRequest { + id: number; + number?: number; + title: string; + state: 'open' | 'closed'; + user: { + login: string; + avatar_url: string; + }; + head: string | { + ref: string; + }; + base: string | { + ref: string; + }; + html_url: string; + created_at: string; + updated_at?: string; + merged: boolean; + merged_at?: string | null; + source?: 'gitea' | 'database'; + environment?: 'non-prod' | 'prod' | 'dev'; + envName?: string; + hasConflicts?: boolean; +} + +export interface Organization { + id: number; + name: string; + description?: string; + createdAt?: string; +} + +export interface Project { + id: number; + name: string; + description?: string; + organizationId: number; + createdAt?: string; +} + +export interface ProjectComponent { + id: number; + projectId: number; + name: string; + title: string; + type: 'UX' | 'API'; + description?: string; + status?: 'Pending' | 'Active' | 'Inactive'; + createdAt?: string; + additional_info?: { + slug?: string; + supported_domains?: string[]; + ai_studio_link?: string; + github_repo?: string; + github_owner?: string; + hiq_repo?: string; + unique_app_code?: string; + [key: string]: any; + }; +} + +export type RequirementType = 'Defect' | 'Feature'; +export type RequirementCategory = 'User' | 'System' | 'Non-Functional'; +// Updated to match backend API validation +export type RequirementStatus = 'New' | 'Open' | 'InProgress' | 'Resolved' | 'Closed'; + +export interface Requirement { + id?: number; + componentId?: number; + type: RequirementType; + category?: RequirementCategory; + title: string; + description: string; + status: RequirementStatus; + tempId?: string; // For frontend tracking of new items before they have a DB ID + unique_hash?: string; +} + +export interface ComponentPrompt { + id?: number; + componentId: number; + title: string; + description: string; + type: 'System' | 'Other'; + content: string; + createdAt?: string; + updatedAt?: string; +} + +export interface ComponentContentItem { + id?: number; + componentId: number; + textId: string; + description: string; + text: string; + format: 'plain' | 'markdown'; + type: 'Title' | 'Body' | 'LinkText' | 'Heading'; + createdAt?: string; + updatedAt?: string; +} + +export interface Task { + id: number; + title: string; + description?: string; + assignee: string; + assigned_date?: string; + due_date: string; + start_date?: string; + close_date?: string; + status: 'Open' | 'InProgress' | 'Closed'; + created_at?: string; + updated_at?: string; + additional_info?: any; +} + +// Added missing LLM and Attachment types +export type ModelProvider = 'google' | 'openai'; + +export interface LLMConfig { + provider: ModelProvider; + model: string; + apiKey?: string; + systemInstruction?: string; +} + +export interface Attachment { + id: string; + name: string; + content: string; + isProcessing: boolean; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..0506f1b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,24 @@ +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; +import {defineConfig, loadEnv} from 'vite'; + +export default defineConfig(({mode}) => { + const env = loadEnv(mode, '.', ''); + return { + plugins: [react(), tailwindcss()], + define: { + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY), + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + }, + }, + server: { + // HMR is disabled in AI Studio via DISABLE_HMR env var. + // Do not modify—file watching is disabled to prevent flickering during agent edits. + hmr: process.env.DISABLE_HMR !== 'true', + }, + }; +}); diff --git a/wrangler.json b/wrangler.json new file mode 100644 index 0000000..a0095fe --- /dev/null +++ b/wrangler.json @@ -0,0 +1,4 @@ +{ + "name": "ai-studio-template", + "compatibility_date": "2025-10-20", +} \ No newline at end of file