From cd8dfb94a306dfc98584e301670421605e077b5c05b97d2e10aaf3c9171597da Mon Sep 17 00:00:00 2001 From: Evan Carroll Date: Sat, 24 Jan 2026 16:37:47 -0600 Subject: [PATCH] fix: avatar support and set it as default --- stock/avatar/create-stock-avatar.sh | 238 -------------- stock/avatar/setup.sh | 299 ++++++++++++++++++ stock/avatar/upload-stockavatars.sh | 174 ---------- stock/index.html | 4 +- stock/load.sh | 5 +- stock/props/keyboard-standard.svg | 67 ++++ ...vg => screen-projector-remote-control.svg} | 0 ...=> screen-projector-screen-with-stand.svg} | 0 ...jector.svg => screen-projector-screen.svg} | 0 9 files changed, 369 insertions(+), 418 deletions(-) delete mode 100755 stock/avatar/create-stock-avatar.sh create mode 100755 stock/avatar/setup.sh delete mode 100755 stock/avatar/upload-stockavatars.sh create mode 100644 stock/props/keyboard-standard.svg rename stock/props/{keyboard-media.svg => screen-projector-remote-control.svg} (100%) rename stock/props/{screen-projector-with-stand.svg => screen-projector-screen-with-stand.svg} (100%) rename stock/props/{screen-projector.svg => screen-projector-screen.svg} (100%) diff --git a/stock/avatar/create-stock-avatar.sh b/stock/avatar/create-stock-avatar.sh deleted file mode 100755 index cdf96aa..0000000 --- a/stock/avatar/create-stock-avatar.sh +++ /dev/null @@ -1,238 +0,0 @@ -#!/bin/bash -# Create a stock avatar from uploaded props and set it as server default. -# -# Usage: ./stock/avatar/create-stock-avatar.sh [--force|-f] [HOST] -# -# Prerequisites: -# 1. Props must be uploaded first: ./stock/avatar/upload-stockavatars.sh -# 2. Dev server must be running: ./run-dev.sh -f -# -# This script: -# 1. Queries existing props by slug to get UUIDs -# 2. Creates a server avatar with all emotion slots populated -# 3. Sets the avatar as the server default for all gender/age combinations - -set -e - -# Parse arguments -FORCE="" -HOST="http://localhost:3001" -DB="chattyness" - -for arg in "$@"; do - case "$arg" in - --force|-f) - FORCE="?force=true" - ;; - http://*) - HOST="$arg" - ;; - esac -done - -echo "==========================================" -echo "Creating Stock Avatar" -echo "==========================================" -echo "Host: $HOST" -echo "Database: $DB" -echo "" - -# Check if server is running -echo "Checking server health..." -health_response=$(curl -s -o /dev/null -w "%{http_code}" "$HOST/api/admin/health" 2>/dev/null || echo "000") -if [ "$health_response" != "200" ]; then - echo "ERROR: Server is not responding at $HOST (HTTP $health_response)" - echo "Make sure the server is running: ./run-dev.sh -f" - exit 1 -fi -echo "Server is healthy!" -echo "" - -# Query prop UUIDs by slug -echo "Querying prop UUIDs..." - -get_prop_id() { - local slug="$1" - psql -d "$DB" -t -A -c "SELECT id FROM server.props WHERE slug = '$slug'" 2>/dev/null | tr -d '[:space:]' -} - -# Get face prop (skin layer) -FACE_ID=$(get_prop_id "face") -if [ -z "$FACE_ID" ]; then - echo "ERROR: Face prop not found. Run upload-stockavatars.sh first." - exit 1 -fi -echo " face: $FACE_ID" - -# Get emotion props -NEUTRAL_ID=$(get_prop_id "neutral") -SMILE_ID=$(get_prop_id "smile") # This is "happy" emotion -SAD_ID=$(get_prop_id "sad") -ANGRY_ID=$(get_prop_id "angry") -SURPRISED_ID=$(get_prop_id "surprised") -THINKING_ID=$(get_prop_id "thinking") -LAUGHING_ID=$(get_prop_id "laughing") -CRYING_ID=$(get_prop_id "crying") -LOVE_ID=$(get_prop_id "love") -CONFUSED_ID=$(get_prop_id "confused") -SLEEPING_ID=$(get_prop_id "sleeping") -WINK_ID=$(get_prop_id "wink") - -# Validate all emotion props exist -missing="" -[ -z "$NEUTRAL_ID" ] && missing="$missing neutral" -[ -z "$SMILE_ID" ] && missing="$missing smile" -[ -z "$SAD_ID" ] && missing="$missing sad" -[ -z "$ANGRY_ID" ] && missing="$missing angry" -[ -z "$SURPRISED_ID" ] && missing="$missing surprised" -[ -z "$THINKING_ID" ] && missing="$missing thinking" -[ -z "$LAUGHING_ID" ] && missing="$missing laughing" -[ -z "$CRYING_ID" ] && missing="$missing crying" -[ -z "$LOVE_ID" ] && missing="$missing love" -[ -z "$CONFUSED_ID" ] && missing="$missing confused" -[ -z "$SLEEPING_ID" ] && missing="$missing sleeping" -[ -z "$WINK_ID" ] && missing="$missing wink" - -if [ -n "$missing" ]; then - echo "ERROR: Missing emotion props:$missing" - echo "Run upload-stockavatars.sh first." - exit 1 -fi - -echo " neutral: $NEUTRAL_ID" -echo " smile (happy): $SMILE_ID" -echo " sad: $SAD_ID" -echo " angry: $ANGRY_ID" -echo " surprised: $SURPRISED_ID" -echo " thinking: $THINKING_ID" -echo " laughing: $LAUGHING_ID" -echo " crying: $CRYING_ID" -echo " love: $LOVE_ID" -echo " confused: $CONFUSED_ID" -echo " sleeping: $SLEEPING_ID" -echo " wink: $WINK_ID" -echo "" - -# Check if avatar already exists -existing_avatar=$(psql -d "$DB" -t -A -c "SELECT id FROM server.avatars WHERE slug = 'stock-avatar'" 2>/dev/null | tr -d '[:space:]') - -if [ -n "$existing_avatar" ] && [ -z "$FORCE" ]; then - echo "Stock avatar already exists with ID: $existing_avatar" - echo "Use --force to recreate it." - AVATAR_ID="$existing_avatar" -else - # Create the avatar via API - echo "Creating stock avatar via API..." - - # Build the JSON payload - avatar_json=$(cat < /dev/null - fi - - # Create the avatar - response=$(curl -s -w "\n%{http_code}" -X POST "$HOST/api/admin/avatars" \ - -H "Content-Type: application/json" \ - -d "$avatar_json") - - http_code=$(echo "$response" | tail -n1) - body=$(echo "$response" | sed '$d') - - if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then - AVATAR_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) - echo " ✓ Created avatar: $AVATAR_ID" - else - echo " ✗ Failed to create avatar (HTTP $http_code): $body" - exit 1 - fi -fi - -echo "" - -# Set as server default for all gender/age combinations -echo "Setting stock avatar as server defaults..." - -psql -d "$DB" -c " -UPDATE server.config SET - default_avatar_neutral_child = '$AVATAR_ID', - default_avatar_neutral_adult = '$AVATAR_ID', - default_avatar_male_child = '$AVATAR_ID', - default_avatar_male_adult = '$AVATAR_ID', - default_avatar_female_child = '$AVATAR_ID', - default_avatar_female_adult = '$AVATAR_ID', - updated_at = now() -WHERE id = '00000000-0000-0000-0000-000000000001' -" > /dev/null - -echo " ✓ Set all 6 default avatar columns" -echo "" - -# Verify -echo "==========================================" -echo "Verification" -echo "==========================================" - -# Check avatar slots -echo "Avatar emotion slots populated:" -psql -d "$DB" -t -c " -SELECT - CASE WHEN l_skin_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' body (l_skin_4)', - CASE WHEN e_neutral_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' neutral', - CASE WHEN e_happy_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' happy', - CASE WHEN e_sad_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' sad', - CASE WHEN e_angry_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' angry', - CASE WHEN e_surprised_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' surprised', - CASE WHEN e_thinking_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' thinking', - CASE WHEN e_laughing_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' laughing', - CASE WHEN e_crying_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' crying', - CASE WHEN e_love_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' love', - CASE WHEN e_confused_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' confused', - CASE WHEN e_sleeping_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' sleeping', - CASE WHEN e_wink_4 IS NOT NULL THEN '✓' ELSE '✗' END || ' wink' -FROM server.avatars WHERE slug = 'stock-avatar' -" | tr '|' '\n' | grep -v '^$' | sed 's/^ */ /' - -echo "" - -# Check server defaults -echo "Server config defaults:" -psql -d "$DB" -t -c " -SELECT - CASE WHEN default_avatar_neutral_adult IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_neutral_adult', - CASE WHEN default_avatar_neutral_child IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_neutral_child', - CASE WHEN default_avatar_male_adult IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_male_adult', - CASE WHEN default_avatar_male_child IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_male_child', - CASE WHEN default_avatar_female_adult IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_female_adult', - CASE WHEN default_avatar_female_child IS NOT NULL THEN '✓' ELSE '✗' END || ' default_avatar_female_child' -FROM server.config WHERE id = '00000000-0000-0000-0000-000000000001' -" | tr '|' '\n' | grep -v '^$' | sed 's/^ */ /' - -echo "" -echo "==========================================" -echo "Stock avatar setup complete!" -echo "Avatar ID: $AVATAR_ID" -echo "==========================================" diff --git a/stock/avatar/setup.sh b/stock/avatar/setup.sh new file mode 100755 index 0000000..d6e90df --- /dev/null +++ b/stock/avatar/setup.sh @@ -0,0 +1,299 @@ +#!/bin/bash +# Set up stock avatar: upload props, create avatar, set as server default. +# +# Usage: ./stock/avatar/setup.sh [--force|-f] [HOST] +# +# Options: +# --force, -f Update existing props/avatar instead of failing on conflict +# +# HOST defaults to http://localhost:3001 (owner admin port) +# +# Prerequisites: +# 1. Run the dev server: ./run-dev.sh -f +# 2. Wait for it to finish building: ./run-dev.sh -s + +set -e + +# Parse arguments +FORCE="" +HOST="http://localhost:3001" + +for arg in "$@"; do + case "$arg" in + --force|-f) + FORCE="?force=true" + ;; + http://*) + HOST="$arg" + ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "==========================================" +echo "Stock Avatar Setup" +echo "==========================================" +echo "Host: $HOST" +echo "Source: $SCRIPT_DIR" +echo "" + +# Check if server is running +echo "Checking server health..." +health_response=$(curl -s -o /dev/null -w "%{http_code}" "$HOST/api/admin/health" 2>/dev/null || echo "000") +if [ "$health_response" != "200" ]; then + echo "ERROR: Server is not responding at $HOST (HTTP $health_response)" + echo "" + echo "Make sure the server is running:" + echo " ./run-dev.sh -f" + echo " ./run-dev.sh -s # Check status" + exit 1 +fi +echo "Server is healthy!" +echo "" + +# ============================================================================= +# Step 1: Upload Props +# ============================================================================= +echo "==========================================" +echo "Step 1: Uploading Props" +echo "==========================================" + +capitalize() { + echo "$1" | sed 's/.*/\u&/' +} + +get_tags() { + local filename="$1" + case "$filename" in + face.svg) + echo '["base", "face"]' + ;; + neutral.svg|smile.svg|sad.svg|angry.svg|surprised.svg|thinking.svg|laughing.svg|crying.svg|love.svg|confused.svg|sleeping.svg|wink.svg) + echo '["face"]' + ;; + *) + echo '["prop"]' + ;; + esac +} + +get_positioning() { + local filename="$1" + case "$filename" in + face.svg) + echo "layer:skin" + ;; + neutral.svg|smile.svg|sad.svg|angry.svg|surprised.svg|thinking.svg|laughing.svg|crying.svg|love.svg|confused.svg|sleeping.svg|wink.svg) + echo "layer:emote" + ;; + *) + echo "none" + ;; + esac +} + +upload_count=0 +upload_fail=0 + +for file in "$SCRIPT_DIR"/*.svg; do + if [ ! -f "$file" ]; then + continue + fi + + filename=$(basename "$file") + name_without_ext="${filename%.svg}" + display_name=$(capitalize "$name_without_ext") + tags=$(get_tags "$filename") + positioning=$(get_positioning "$filename") + + echo " $filename -> $display_name" + + positioning_type="${positioning%%:*}" + positioning_value="${positioning#*:}" + + case "$positioning_type" in + layer) + positioning_json="\"default_layer\": \"$positioning_value\", \"default_position\": 4" + ;; + *) + positioning_json="" + ;; + esac + + if [ -n "$positioning_json" ]; then + metadata="{\"name\": \"$display_name\", \"tags\": $tags, $positioning_json}" + else + metadata="{\"name\": \"$display_name\", \"tags\": $tags}" + fi + + response=$(curl -s -w "\n%{http_code}" -X POST "$HOST/api/admin/props$FORCE" \ + -F "metadata=$metadata" \ + -F "file=@$file") + + http_code=$(echo "$response" | tail -n1) + + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + echo " ✓ Uploaded" + ((++upload_count)) + else + body=$(echo "$response" | sed '$d') + echo " ✗ Failed (HTTP $http_code): $body" + ((++upload_fail)) + fi +done + +echo "" +echo "Props: $upload_count uploaded, $upload_fail failed" +echo "" + +if [ "$upload_fail" -gt 0 ] && [ -z "$FORCE" ]; then + echo "Some props failed. Use --force to update existing props." + exit 1 +fi + +# ============================================================================= +# Step 2: Query Prop UUIDs +# ============================================================================= +echo "==========================================" +echo "Step 2: Creating Avatar" +echo "==========================================" + +# Query prop UUID by slug via API +get_prop_id() { + local slug="$1" + curl -s "$HOST/api/admin/props/by-slug/$slug" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4 +} + +echo "Querying prop UUIDs..." + +FACE_ID=$(get_prop_id "face") +if [ -z "$FACE_ID" ]; then + echo "ERROR: Face prop not found." + exit 1 +fi + +NEUTRAL_ID=$(get_prop_id "neutral") +SMILE_ID=$(get_prop_id "smile") +SAD_ID=$(get_prop_id "sad") +ANGRY_ID=$(get_prop_id "angry") +SURPRISED_ID=$(get_prop_id "surprised") +THINKING_ID=$(get_prop_id "thinking") +LAUGHING_ID=$(get_prop_id "laughing") +CRYING_ID=$(get_prop_id "crying") +LOVE_ID=$(get_prop_id "love") +CONFUSED_ID=$(get_prop_id "confused") +SLEEPING_ID=$(get_prop_id "sleeping") +WINK_ID=$(get_prop_id "wink") + +# Validate all props exist +missing="" +[ -z "$NEUTRAL_ID" ] && missing="$missing neutral" +[ -z "$SMILE_ID" ] && missing="$missing smile" +[ -z "$SAD_ID" ] && missing="$missing sad" +[ -z "$ANGRY_ID" ] && missing="$missing angry" +[ -z "$SURPRISED_ID" ] && missing="$missing surprised" +[ -z "$THINKING_ID" ] && missing="$missing thinking" +[ -z "$LAUGHING_ID" ] && missing="$missing laughing" +[ -z "$CRYING_ID" ] && missing="$missing crying" +[ -z "$LOVE_ID" ] && missing="$missing love" +[ -z "$CONFUSED_ID" ] && missing="$missing confused" +[ -z "$SLEEPING_ID" ] && missing="$missing sleeping" +[ -z "$WINK_ID" ] && missing="$missing wink" + +if [ -n "$missing" ]; then + echo "ERROR: Missing props:$missing" + exit 1 +fi + +echo " ✓ All 13 props found" + +# ============================================================================= +# Step 3: Create Avatar +# ============================================================================= + +# Check if avatar already exists +existing_avatar=$(curl -s "$HOST/api/admin/avatars/by-slug/stock-avatar" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) + +if [ -n "$existing_avatar" ] && [ -z "$FORCE" ]; then + echo "Stock avatar already exists: $existing_avatar" + echo "Use --force to recreate." + AVATAR_ID="$existing_avatar" +else + avatar_json=$(cat < /dev/null + fi + + response=$(curl -s -w "\n%{http_code}" -X POST "$HOST/api/admin/avatars" \ + -H "Content-Type: application/json" \ + -d "$avatar_json") + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + AVATAR_ID=$(echo "$body" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) + echo " ✓ Created avatar: $AVATAR_ID" + else + echo " ✗ Failed (HTTP $http_code): $body" + exit 1 + fi +fi + +# ============================================================================= +# Step 4: Set as Server Default +# ============================================================================= +echo "" +echo "==========================================" +echo "Step 3: Setting Server Defaults" +echo "==========================================" + +response=$(curl -s -w "\n%{http_code}" -X PATCH "$HOST/api/admin/config/default-avatars" \ + -H "Content-Type: application/json" \ + -d "{ + \"default_avatar_neutral_child\": \"$AVATAR_ID\", + \"default_avatar_neutral_adult\": \"$AVATAR_ID\", + \"default_avatar_male_child\": \"$AVATAR_ID\", + \"default_avatar_male_adult\": \"$AVATAR_ID\", + \"default_avatar_female_child\": \"$AVATAR_ID\", + \"default_avatar_female_adult\": \"$AVATAR_ID\" + }") + +http_code=$(echo "$response" | tail -n1) +if [ "$http_code" = "200" ]; then + echo " ✓ Set all 6 default avatar columns" +else + echo " ✗ Failed (HTTP $http_code)" + exit 1 +fi + +echo "" +echo "==========================================" +echo "Stock Avatar Setup Complete!" +echo "==========================================" +echo "Avatar ID: $AVATAR_ID" +echo "" diff --git a/stock/avatar/upload-stockavatars.sh b/stock/avatar/upload-stockavatars.sh deleted file mode 100755 index 1670fcb..0000000 --- a/stock/avatar/upload-stockavatars.sh +++ /dev/null @@ -1,174 +0,0 @@ -#!/bin/bash -# Upload all stock avatar props to the server. -# -# Usage: ./stockavatar/upload-stockavatars.sh [--force|-f] [HOST] -# -# Options: -# --force, -f Update existing props instead of failing with 409 Conflict -# -# HOST defaults to http://localhost:3001 (owner admin port) -# -# Prerequisites: -# 1. Run the dev server: ./run-dev.sh -f -# 2. Wait for it to finish building: ./run-dev.sh -s -# -# The owner admin server (port 3001) uses the chattyness_owner DB role -# which bypasses RLS, so no authentication is required. - -set -e - -# Parse arguments -FORCE="" -HOST="http://localhost:3001" - -for arg in "$@"; do - case "$arg" in - --force|-f) - FORCE="?force=true" - ;; - http://*) - HOST="$arg" - ;; - esac -done -# Script directory is the stockavatar directory -STOCKAVATAR_DIR="$(cd "$(dirname "$0")" && pwd)" - -echo "Uploading stock avatars to $HOST/api/admin/props" -echo "Source directory: $STOCKAVATAR_DIR" -echo "" - -# Check if server is running -echo "Checking server health..." -health_response=$(curl -s -o /dev/null -w "%{http_code}" "$HOST/api/admin/health" 2>/dev/null || echo "000") -if [ "$health_response" != "200" ]; then - echo "ERROR: Server is not responding at $HOST (HTTP $health_response)" - echo "" - echo "Make sure the server is running:" - echo " ./run-dev.sh -f" - echo " ./run-dev.sh -s # Check status" - exit 1 -fi -echo "Server is healthy!" -echo "" - -# Function to capitalize first letter -capitalize() { - echo "$1" | sed 's/.*/\u&/' -} - -# Function to determine tags based on filename -# Tags complement default_layer - avoid redundant info -get_tags() { - local filename="$1" - case "$filename" in - face.svg) - # Base face prop - "skin" is already in default_layer - echo '["base", "face"]' - ;; - neutral.svg | smile.svg | sad.svg | angry.svg | surprised.svg | thinking.svg | laughing.svg | crying.svg | love.svg | confused.svg | sleeping.svg | wink.svg) - # Facial expression props - "emote" is already in default_layer - echo '["face"]' - ;; - *) - echo '["prop"]' - ;; - esac -} - -# Function to get positioning fields based on filename -# Returns: "layer:" for content layer props, "none" for generic props -get_positioning() { - local filename="$1" - case "$filename" in - face.svg) - # Base face is a content layer prop (skin layer) - echo "layer:skin" - ;; - neutral.svg | smile.svg | sad.svg | angry.svg | surprised.svg | thinking.svg | laughing.svg | crying.svg | love.svg | confused.svg | sleeping.svg | wink.svg) - # Facial expression props use the emote layer - echo "layer:emote" - ;; - *) - echo "none" - ;; - esac -} - -# Track success/failure counts -success_count=0 -fail_count=0 - -# Upload each SVG file -for file in "$STOCKAVATAR_DIR"/*.svg; do - if [ ! -f "$file" ]; then - continue - fi - - filename=$(basename "$file") - name_without_ext="${filename%.svg}" - display_name=$(capitalize "$name_without_ext") - tags=$(get_tags "$filename") - positioning=$(get_positioning "$filename") - - echo "Uploading: $filename -> $display_name (positioning: $positioning)" - - # Build positioning fields based on type - positioning_type="${positioning%%:*}" - positioning_value="${positioning#*:}" - - case "$positioning_type" in - layer) - # Content layer prop (skin, clothes, accessories, emote) - positioning_json="\"default_layer\": \"$positioning_value\", \"default_position\": 4" - ;; - *) - # Generic prop (no default positioning) - positioning_json="" - ;; - esac - - # Create metadata JSON - if [ -n "$positioning_json" ]; then - metadata=$( - cat < + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stock/props/keyboard-media.svg b/stock/props/screen-projector-remote-control.svg similarity index 100% rename from stock/props/keyboard-media.svg rename to stock/props/screen-projector-remote-control.svg diff --git a/stock/props/screen-projector-with-stand.svg b/stock/props/screen-projector-screen-with-stand.svg similarity index 100% rename from stock/props/screen-projector-with-stand.svg rename to stock/props/screen-projector-screen-with-stand.svg diff --git a/stock/props/screen-projector.svg b/stock/props/screen-projector-screen.svg similarity index 100% rename from stock/props/screen-projector.svg rename to stock/props/screen-projector-screen.svg