Repository: ArcTanSusan/tell-me-a-joke
Pull request #3: Feat/open ai integration
Author: login: brolag
Created: 2025-03-16T03:12:54Z | Merged: 2025-03-17T02:59:56Z

This PR integrates Open AI and updates the UI.

Demo:
https://www.loom.com/share/8e7d47754e7d4de48fdaa6613291a574

<img width="719" alt="image" src="https://github.com/user-attachments/assets/2027cf9a-2f05-4b9d-be9e-3ab7599437d5" />



Diff:
diff --git a/.gitignore b/.gitignore
index 5ef6a52..0581257 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,6 @@ yarn-error.log*
 # typescript
 *.tsbuildinfo
 next-env.d.ts
+
+# instructions
+instructions/
diff --git a/package-lock.json b/package-lock.json
index eb24108..dfc26e2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,31 +8,37 @@
       "name": "tell-me-a-joke",
       "version": "0.1.0",
       "dependencies": {
+        "@ai-sdk/provider": "^1.0.11",
+        "@ai-sdk/provider-utils": "^2.1.13",
+        "@ai-sdk/ui-utils": "^1.1.19",
         "@radix-ui/react-label": "^2.1.2",
         "@radix-ui/react-select": "^2.1.6",
         "@radix-ui/react-slider": "^1.2.3",
         "@radix-ui/react-slot": "^1.1.2",
-        "ai": "^4.1.61",
+        "@vercel/analytics": "^1.5.0",
+        "ai": "^3.0.19",
         "class-variance-authority": "^0.7.1",
         "clsx": "^2.1.1",
         "lucide-react": "^0.479.0",
         "next": "15.2.2",
         "openai": "^4.87.3",
+        "openai-edge": "^1.2.2",
         "react": "^19.0.0",
         "react-dom": "^19.0.0",
         "tailwind-merge": "^3.0.2",
-        "tailwindcss-animate": "^1.0.7"
+        "tailwindcss-animate": "^1.0.7",
+        "zod": "^3.24.2"
       },
       "devDependencies": {
         "@eslint/eslintrc": "^3",
         "@tailwindcss/postcss": "^4",
         "@types/node": "^20",
-        "@types/react": "^19",
+        "@types/react": "19.0.10",
         "@types/react-dom": "^19",
         "eslint": "^9",
         "eslint-config-next": "15.2.2",
         "tailwindcss": "^4",
-        "typescript": "^5"
+        "typescript": "5.8.2"
       }
     },
     "node_modules/@ai-sdk/provider": {
@@ -70,33 +76,6 @@
         }
       }
     },
-    "node_modules/@ai-sdk/react": {
-      "version": "1.1.23",
-      "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.1.23.tgz",
-      "integrity": "sha512-R+PG9ya0GLs6orzt+1MxmjrWFuZM0gVs+l8ihBr1u+42wwkVeojY4CAtQjW4nrfGTVbdJYkl5y+r/VKfjr42aQ==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "@ai-sdk/provider-utils": "2.1.13",
-        "@ai-sdk/ui-utils": "1.1.19",
-        "swr": "^2.2.5",
-        "throttleit": "2.1.0"
-      },
-      "engines": {
-        "node": ">=18"
-      },
-      "peerDependencies": {
-        "react": "^18 || ^19 || ^19.0.0-rc",
-        "zod": "^3.0.0"
-      },
-      "peerDependenciesMeta": {
-        "react": {
-          "optional": true
-        },
-        "zod": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/@ai-sdk/ui-utils": {
       "version": "1.1.19",
       "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.1.19.tgz",
@@ -937,15 +916,6 @@
         "node": ">=12.4.0"
       }
     },
-    "node_modules/@opentelemetry/api": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
-      "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
-      "license": "Apache-2.0",
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
     "node_modules/@radix-ui/number": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
@@ -1762,7 +1732,6 @@
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
       "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/@types/json5": {
@@ -2047,6 +2016,44 @@
         "url": "https://opencollective.com/typescript-eslint"
       }
     },
+    "node_modules/@vercel/analytics": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.5.0.tgz",
+      "integrity": "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==",
+      "license": "MPL-2.0",
+      "peerDependencies": {
+        "@remix-run/react": "^2",
+        "@sveltejs/kit": "^1 || ^2",
+        "next": ">= 13",
+        "react": "^18 || ^19 || ^19.0.0-rc",
+        "svelte": ">= 4",
+        "vue": "^3",
+        "vue-router": "^4"
+      },
+      "peerDependenciesMeta": {
+        "@remix-run/react": {
+          "optional": true
+        },
+        "@sveltejs/kit": {
+          "optional": true
+        },
+        "next": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "svelte": {
+          "optional": true
+        },
+        "vue": {
+          "optional": true
+        },
+        "vue-router": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/abort-controller": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -2095,35 +2102,88 @@
       }
     },
     "node_modules/ai": {
-      "version": "4.1.61",
-      "resolved": "https://registry.npmjs.org/ai/-/ai-4.1.61.tgz",
-      "integrity": "sha512-Y9SAyGJEeW23F6C7PSHZXYNEvbH2cqJm0rVW2AoeFaXFT13ttx8rAqs8wz2w466C1UB329yl5PXayFcHqofSEA==",
+      "version": "3.0.19",
+      "resolved": "https://registry.npmjs.org/ai/-/ai-3.0.19.tgz",
+      "integrity": "sha512-HV0MixfqMrxq2N/gVsEXoNfO9e0mWkgv7zdUH6UvdtI53Qd0UjoxjH0bwe/TsxoCqnK4gnWaGYiGKZrpy/Ldtw==",
       "license": "Apache-2.0",
       "dependencies": {
-        "@ai-sdk/provider": "1.0.11",
-        "@ai-sdk/provider-utils": "2.1.13",
-        "@ai-sdk/react": "1.1.23",
-        "@ai-sdk/ui-utils": "1.1.19",
-        "@opentelemetry/api": "1.9.0",
-        "eventsource-parser": "^3.0.0",
-        "jsondiffpatch": "0.6.0"
+        "@types/json-schema": "7.0.15",
+        "eventsource-parser": "1.1.2",
+        "json-schema": "0.4.0",
+        "jsondiffpatch": "0.6.0",
+        "nanoid": "3.3.6",
+        "secure-json-parse": "2.7.0",
+        "solid-swr-store": "0.10.7",
+        "sswr": "2.0.0",
+        "swr": "2.2.0",
+        "swr-store": "0.10.6",
+        "swrv": "1.0.4",
+        "zod-to-json-schema": "3.22.5"
       },
       "engines": {
-        "node": ">=18"
+        "node": ">=14.6"
       },
       "peerDependencies": {
-        "react": "^18 || ^19 || ^19.0.0-rc",
+        "react": "^18.2.0",
+        "solid-js": "^1.7.7",
+        "svelte": "^3.0.0 || ^4.0.0",
+        "vue": "^3.3.4",
         "zod": "^3.0.0"
       },
       "peerDependenciesMeta": {
         "react": {
           "optional": true
         },
+        "solid-js": {
+          "optional": true
+        },
+        "svelte": {
+          "optional": true
+        },
+        "vue": {
+          "optional": true
+        },
         "zod": {
           "optional": true
         }
       }
     },
+    "node_modules/ai/node_modules/eventsource-parser": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
+      "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.18"
+      }
+    },
+    "node_modules/ai/node_modules/nanoid": {
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+      "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/ai/node_modules/zod-to-json-schema": {
+      "version": "3.22.5",
+      "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz",
+      "integrity": "sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==",
+      "license": "ISC",
+      "peerDependencies": {
+        "zod": "^3.22.4"
+      }
+    },
     "node_modules/ajv": {
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -5254,6 +5314,15 @@
         }
       }
     },
+    "node_modules/openai-edge": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/openai-edge/-/openai-edge-1.2.2.tgz",
+      "integrity": "sha512-C3/Ao9Hkx5uBPv9YFBpX/x59XMPgPUU4dyGg/0J2sOJ7O9D98kD+lfdOc7v/60oYo5xzMGct80uFkYLH+X2qgw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/openai/node_modules/@types/node": {
       "version": "18.19.80",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz",
@@ -5983,6 +6052,19 @@
         "is-arrayish": "^0.3.1"
       }
     },
+    "node_modules/solid-swr-store": {
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/solid-swr-store/-/solid-swr-store-0.10.7.tgz",
+      "integrity": "sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "solid-js": "^1.2",
+        "swr-store": "^0.10"
+      }
+    },
     "node_modules/source-map-js": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5992,6 +6074,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/sswr": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/sswr/-/sswr-2.0.0.tgz",
+      "integrity": "sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==",
+      "license": "MIT",
+      "dependencies": {
+        "swrev": "^4.0.0"
+      },
+      "peerDependencies": {
+        "svelte": "^4.0.0"
+      }
+    },
     "node_modules/stable-hash": {
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz",
@@ -6193,16 +6287,42 @@
       }
     },
     "node_modules/swr": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz",
-      "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz",
+      "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==",
+      "license": "MIT",
+      "dependencies": {
+        "use-sync-external-store": "^1.2.0"
+      },
+      "peerDependencies": {
+        "react": "^16.11.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
+    "node_modules/swr-store": {
+      "version": "0.10.6",
+      "resolved": "https://registry.npmjs.org/swr-store/-/swr-store-0.10.6.tgz",
+      "integrity": "sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==",
       "license": "MIT",
       "dependencies": {
-        "dequal": "^2.0.3",
-        "use-sync-external-store": "^1.4.0"
+        "dequal": "^2.0.3"
       },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/swrev": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz",
+      "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==",
+      "license": "MIT"
+    },
+    "node_modules/swrv": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz",
+      "integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==",
+      "license": "Apache-2.0",
       "peerDependencies": {
-        "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+        "vue": ">=3.2.26 < 4"
       }
     },
     "node_modules/tailwind-merge": {
@@ -6241,18 +6361,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/throttleit": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
-      "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=18"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/tinyglobby": {
       "version": "0.2.12",
       "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
@@ -6694,10 +6802,19 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/zod": {
+      "version": "3.24.2",
+      "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
+      "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
+    },
     "node_modules/zod-to-json-schema": {
-      "version": "3.24.3",
-      "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz",
-      "integrity": "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==",
+      "version": "3.24.4",
+      "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.4.tgz",
+      "integrity": "sha512-0uNlcvgabyrni9Ag8Vghj21drk7+7tp7VTwwR7KxxXXc/3pbXz2PHlDgj3cICahgF1kHm4dExBFj7BXrZJXzig==",
       "license": "ISC",
       "peerDependencies": {
         "zod": "^3.24.1"
diff --git a/package.json b/package.json
index e835b61..88661f9 100644
--- a/package.json
+++ b/package.json
@@ -9,30 +9,36 @@
     "lint": "next lint"
   },
   "dependencies": {
+    "@ai-sdk/provider": "^1.0.11",
+    "@ai-sdk/provider-utils": "^2.1.13",
+    "@ai-sdk/ui-utils": "^1.1.19",
     "@radix-ui/react-label": "^2.1.2",
     "@radix-ui/react-select": "^2.1.6",
     "@radix-ui/react-slider": "^1.2.3",
     "@radix-ui/react-slot": "^1.1.2",
-    "ai": "^4.1.61",
+    "@vercel/analytics": "^1.5.0",
+    "ai": "^3.0.19",
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
     "lucide-react": "^0.479.0",
     "next": "15.2.2",
     "openai": "^4.87.3",
+    "openai-edge": "^1.2.2",
     "react": "^19.0.0",
     "react-dom": "^19.0.0",
     "tailwind-merge": "^3.0.2",
-    "tailwindcss-animate": "^1.0.7"
+    "tailwindcss-animate": "^1.0.7",
+    "zod": "^3.24.2"
   },
   "devDependencies": {
     "@eslint/eslintrc": "^3",
     "@tailwindcss/postcss": "^4",
     "@types/node": "^20",
-    "@types/react": "^19",
+    "@types/react": "19.0.10",
     "@types/react-dom": "^19",
     "eslint": "^9",
     "eslint-config-next": "15.2.2",
     "tailwindcss": "^4",
-    "typescript": "^5"
+    "typescript": "5.8.2"
   }
 }
diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts
deleted file mode 100644
index 57bd467..0000000
--- a/src/app/api/chat/route.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import OpenAI from "openai";
-import { OpenAIStream, StreamingTextResponse } from "ai";
-
-const openai = new OpenAI();
-
-export const runtime = "edge";
-
-export async function POST(req: Request) {
-  const { messages } = await req.json();
-
-  const response = await openai.chat.completions.create({
-    model: "gpt-4o-mini",
-    stream: true,
-    messages: [
-      {
-        role: "system",
-        content: `You are a professional comedian who is excellente at telling jokes and making people laugh.`,
-      },
-      ...messages,
-    ],
-  });
-
-  const stream = OpenAIStream(response);
-  return new StreamingTextResponse(stream);
-}
\ No newline at end of file
diff --git a/src/app/api/joke/route.ts b/src/app/api/joke/route.ts
new file mode 100644
index 0000000..ee278bc
--- /dev/null
+++ b/src/app/api/joke/route.ts
@@ -0,0 +1,68 @@
+import { Configuration, OpenAIApi } from 'openai-edge';
+
+// Create an OpenAI API client (that's edge-friendly!)
+const config = new Configuration({
+  apiKey: process.env.OPENAI_API_KEY,
+});
+const openai = new OpenAIApi(config);
+
+// IMPORTANT: Set the runtime to edge for streaming responses
+export const runtime = 'edge';
+
+export async function POST(req: Request) {
+  try {
+    const { messages, parameters } = await req.json();
+    const { topic, tone, jokeType, temperature } = parameters;
+
+    // Enhanced system and user prompts for better joke generation
+    const systemPrompt = `You are a world-class stand-up comedian known for your perfect timing and clever humor. You excel at:
+                          - Crafting ${tone} jokes that land perfectly
+                          - Creating unexpected but delightful punchlines
+                          - Reading the room and keeping things appropriate
+                          - Making complex topics simple and funny
+                          - Using perfect setup-punchline timing
+
+                          Your jokes are always original, quotable, and leave people laughing.`;
+
+    const prompt = `Create a ${tone} joke that will genuinely make people laugh.${
+      topic !== 'anything' ? ` The topic is "${topic}" - use it in a clever, unexpected way.` : ' Choose a relatable topic.'
+    }${
+      jokeType !== 'any' ? ` Structure it as a ${jokeType} joke with perfect timing.` : ' Pick the most effective joke format.'
+    } Focus on the punchline and make it memorable.`;
+
+    const response = await openai.createChatCompletion({
+      model: 'gpt-4o',
+      stream: true,
+      messages: [
+        {
+          role: 'system',
+          content: systemPrompt
+        },
+        ...messages,
+        {
+          role: 'user',
+          content: prompt
+        }
+      ],
+      temperature: temperature,
+    });
+
+    // Return the response with streaming headers
+    return new Response(response.body, {
+      headers: {
+        'Content-Type': 'text/event-stream',
+        'Cache-Control': 'no-cache',
+        'Connection': 'keep-alive',
+      },
+    });
+  } catch (error) {
+    console.error('Error:', error);
+    return new Response(
+      JSON.stringify({ error: 'There was an error generating your joke' }),
+      { 
+        status: 500,
+        headers: { 'Content-Type': 'application/json' }
+      }
+    );
+  }
+} 
\ No newline at end of file
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
index 718d6fe..c18703b 100644
Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..3edbea9 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,32 +1,22 @@
 import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Inter } from "next/font/google";
 import "./globals.css";
 
-const geistSans = Geist({
-  variable: "--font-geist-sans",
-  subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
-  variable: "--font-geist-mono",
-  subsets: ["latin"],
-});
+const inter = Inter({ subsets: ["latin"] });
 
 export const metadata: Metadata = {
-  title: "Create Next App",
-  description: "Generated by create next app",
+  title: "Tell Me A Joke - AI Joke Generator",
+  description: "Generate hilarious jokes with AI",
 };
 
 export default function RootLayout({
   children,
-}: Readonly<{
-  children: React.ReactNode;
-}>) {
+}: {
+  children: React.ReactNode
+}) {
   return (
-    <html lang="en">
-      <body
-        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
-      >
+    <html lang="en" className="dark">
+      <body className={`${inter.className} min-h-screen bg-black text-white antialiased`}>
         {children}
       </body>
     </html>
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 8a39b74..6a0b645 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,22 +1,92 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
 
 export default function Home() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      const savedSettings = localStorage.getItem('jokeSettings')
+      if (!savedSettings) {
+        console.log('No saved settings found, using defaults')
+        setSettings(defaultSettings)
+        setIsLoading(false)
+        return
+      }
+      
+      const parsed = JSON.parse(savedSettings) 
+      
+      // Merge with defaults to ensure all fields exist
+      const mergedSettings = {
+        topic: parsed.topic || defaultSettings.topic,
+        tone: parsed.tone || defaultSettings.tone,
+        jokeType: parsed.jokeType || defaultSettings.jokeType,
+        temperature: parsed.temperature ?? defaultSettings.temperature
+      }
+      
+      console.log('Loaded settings:', mergedSettings)
+      setSettings(mergedSettings)
+      setIsLoading(false)
+    }
+
+    loadSettings()
+  }, [])
+
   return (
-    <main className="min-h-screen bg-gradient-to-b from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800 py-12 px-4 sm:px-6">
-      <div className="max-w-4xl mx-auto">
-        <div className="text-center mb-12">
-          <h1 className="text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white sm:text-5xl md:text-6xl">
-            <span className="block">Joke Generator</span>
-            <span className="block text-purple-600 dark:text-purple-400">Laugh on Demand</span>
-          </h1>
-          <p className="mt-3 max-w-md mx-auto text-base text-gray-500 dark:text-gray-400 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
-            Customize your joke parameters and get instant humor tailored just for you!
-          </p>
+    <div className="flex flex-col min-h-screen bg-gradient-to-b from-black to-amber-900/40">
+      {/* Header */}
+      <header className="p-4 flex justify-between items-center">
+        <h1 className="text-xl font-bold">
+          Tell Me A<span className="text-orange-500"> Joke</span>
+        </h1>
+        <Link href="/settings">
+          <Button variant="ghost" size="icon" className="text-gray-400 hover:text-white">
+            <Settings className="h-5 w-5" />
+            <span className="sr-only">Settings</span>
+          </Button>
+        </Link>
+      </header>
+
+      {/* Main content */}
+      <main className="flex-1 flex flex-col items-center justify-center p-6">
+        <div className="w-full max-w-xl mx-auto">
+          {isLoading ? (
+            <div className="flex items-center justify-center gap-2 text-gray-400">
+              <Loader2 className="h-5 w-5 animate-spin" />
+              <span>Loading settings...</span>
+            </div>
+          ) : (
+            <>
+              {error && (
+                <div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4 mb-4 flex items-center gap-2 text-red-400">
+                  <AlertCircle className="h-5 w-5 flex-shrink-0" />
+                  <p>{error}</p>
+                </div>
+              )}
+              <SimpleJokeDisplay settings={settings} />
+            </>
+          )}
         </div>
+      </main>
 
-        <JokeGenerator />
-      </div>
-    </main>
+      {/* Footer */}
+      <footer className="p-4 text-center text-sm text-gray-400">
+        <p>Powered by AI • Made with ❤️</p>
+      </footer>
+    </div>
   )
 }
 
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx
new file mode 100644
index 0000000..e33f5f1
--- /dev/null
+++ b/src/app/settings/page.tsx
@@ -0,0 +1,200 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Slider } from "@/components/ui/slider"
+import { ArrowLeft, Loader2, AlertCircle } from "lucide-react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings, jokeTopics, jokeTones, jokeTypes } from "@/types/settings"
+import { SettingsCard } from "@/components/settings-card"
+
+
+export default function Settings() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+  const [saved, setSaved] = useState(false)
+
+  // Load settings on mount
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      const savedSettings = localStorage.getItem('jokeSettings')
+      if (!savedSettings) {
+        console.log('No saved settings found, using defaults')
+        setSettings(defaultSettings)
+        setIsLoading(false)
+        return
+      }
+      
+      const parsed = JSON.parse(savedSettings)
+      
+      // Merge with defaults to ensure all fields exist
+      const mergedSettings = {
+        topic: parsed.topic || defaultSettings.topic,
+        tone: parsed.tone || defaultSettings.tone,
+        jokeType: parsed.jokeType || defaultSettings.jokeType,
+        temperature: parsed.temperature ?? defaultSettings.temperature
+      }
+      
+      console.log('Loaded settings:', mergedSettings)
+      setSettings(mergedSettings)
+      setIsLoading(false)
+    }
+
+    loadSettings()
+  }, [])
+
+  const saveSettings = () => {
+    setError(null)
+    localStorage.setItem('jokeSettings', JSON.stringify(settings))
+    console.log('Saved settings:', settings)
+    setSaved(true)
+  }
+
+  return (
+    <div className="flex flex-col min-h-screen bg-gradient-to-b from-black to-amber-900/40">
+      {/* Header */}
+      <header className="p-4 flex items-center gap-4">
+        <Link href="/">
+          <Button variant="ghost" size="icon" className="text-gray-400 hover:text-white">
+            <ArrowLeft className="h-5 w-5" />
+            <span className="sr-only">Back</span>
+          </Button>
+        </Link>
+        <h1 className="text-xl font-bold">
+          Tell Me A<span className="text-orange-500"> Joke</span>
+        </h1>
+      </header>
+
+      {/* Main content */}
+      <main className="flex-1 flex flex-col items-center justify-center p-6">
+        <div className="w-full max-w-md space-y-8">
+          {isLoading ? (
+            <div className="flex items-center justify-center gap-2 text-gray-400">
+              <Loader2 className="h-5 w-5 animate-spin" />
+              <span>Loading settings...</span>
+            </div>
+          ) : (
+            <>
+              {error && (
+                <div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4 mb-4 flex items-center gap-2 text-red-400">
+                  <AlertCircle className="h-5 w-5 flex-shrink-0" />
+                  <p>{error}</p>
+                </div>
+              )}
+              
+              <SettingsCard settings={settings} />
+
+              {/* Settings Form */}
+              <div className="bg-black/20 border border-amber-800/30 rounded-3xl p-6 backdrop-blur-sm">
+                <form className="space-y-6" onSubmit={(e) => { e.preventDefault(); saveSettings(); }}>
+                  <div className="space-y-2">
+                    <Label htmlFor="topic" className="text-gray-300 flex items-center gap-2">
+                      Topic
+                    </Label>
+                    <Select 
+                      value={settings.topic} 
+                      onValueChange={(value) => setSettings(prev => ({ ...prev, topic: value }))}
+                    >
+                      <SelectTrigger id="topic" className="bg-transparent border-amber-800/30">
+                        <SelectValue placeholder="Select a topic" />
+                      </SelectTrigger>
+                      <SelectContent className="bg-gray-900">
+                        {jokeTopics.map((topic) => (
+                          <SelectItem key={topic} value={topic}>
+                            {topic.charAt(0).toUpperCase() + topic.slice(1)}
+                          </SelectItem>
+                        ))}
+                      </SelectContent>
+                    </Select>
+                  </div>
+
+                  <div className="space-y-2">
+                    <Label htmlFor="tone" className="text-gray-300 flex items-center gap-2">
+                      Tone
+                    </Label>
+                    <Select 
+                      value={settings.tone} 
+                      onValueChange={(value) => setSettings(prev => ({ ...prev, tone: value }))}
+                    >
+                      <SelectTrigger id="tone" className="bg-transparent border-amber-800/30">
+                        <SelectValue placeholder="Select a tone" />
+                      </SelectTrigger>
+                      <SelectContent className="bg-gray-900">
+                        {jokeTones.map((tone) => (
+                          <SelectItem key={tone} value={tone}>
+                            {tone.charAt(0).toUpperCase() + tone.slice(1)}
+                          </SelectItem>
+                        ))}
+                      </SelectContent>
+                    </Select>
+                  </div>
+
+                  <div className="space-y-2">
+                    <Label htmlFor="joke-type" className="text-gray-300 flex items-center gap-2">
+                      Type
+                    </Label>
+                    <Select 
+                      value={settings.jokeType} 
+                      onValueChange={(value) => setSettings(prev => ({ ...prev, jokeType: value }))}
+                    >
+                      <SelectTrigger id="joke-type" className="bg-transparent border-amber-800/30">
+                        <SelectValue placeholder="Select a joke type" />
+                      </SelectTrigger>
+                      <SelectContent className="bg-gray-900">
+                        {jokeTypes.map((type) => (
+                          <SelectItem key={type} value={type}>
+                            {type.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('-')}
+                          </SelectItem>
+                        ))}
+                      </SelectContent>
+                    </Select>
+                  </div>
+
+                  <div className="space-y-4">
+                    <div className="flex justify-between items-center">
+                      <Label htmlFor="temperature" className="text-gray-300 flex items-center gap-2">
+                        Creativity
+                      </Label>
+                      <span className="text-orange-400 font-medium">{Math.round(settings.temperature * 100)}%</span>
+                    </div>
+                    <Slider
+                      id="temperature"
+                      min={0.1}
+                      max={1.0}
+                      step={0.1}
+                      value={[settings.temperature]}
+                      onValueChange={([value]) => setSettings(prev => ({ ...prev, temperature: value }))}
+                      className="w-full"
+                    />
+                    <div className="flex justify-between text-xs text-gray-400">
+                      <span>Predictable</span>
+                      <span>Creative</span>
+                    </div>
+                  </div>
+
+                  <Button
+                    type="submit"
+                    className={`w-full rounded-full p-6 text-xl font-bold transition-all transform hover:scale-105 ${
+                      saved 
+                        ? 'bg-green-500 hover:bg-green-600 text-white'
+                        : 'bg-orange-500 hover:bg-orange-600 text-white'
+                    }`}
+                  >
+                    {saved ? '✨ Settings Saved!' : 'Save Settings'}
+                  </Button>
+                </form>
+              </div>
+            </>
+          )}
+        </div>
+      </main>
+    </div>
+  )
+} 
\ No newline at end of file
diff --git a/src/components/joke-generator.tsx b/src/components/joke-generator.tsx
deleted file mode 100644
index d6eb0c6..0000000
--- a/src/components/joke-generator.tsx
+++ /dev/null
@@ -1,204 +0,0 @@
-"use client"
-
-import { useState } from "react"
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Label } from "@/components/ui/label"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { Slider } from "@/components/ui/slider"
-import { Loader2, RefreshCw } from "lucide-react"
-
-// Mock jokes for demonstration
-const mockJokes = [
-  "Why don't scientists trust atoms? Because they make up everything!",
-  "I told my wife she was drawing her eyebrows too high. She looked surprised.",
-  "Parallel lines have so much in common. It's a shame they'll never meet.",
-  "Why did the scarecrow win an award? Because he was outstanding in his field!",
-  "I'm reading a book about anti-gravity. It's impossible to put down!",
-  "Did you hear about the mathematician who's afraid of negative numbers? He'll stop at nothing to avoid them.",
-  "Why don't eggs tell jokes? They'd crack each other up.",
-  "What's the best thing about Switzerland? I don't know, but the flag is a big plus.",
-  "I used to play piano by ear, but now I use my hands.",
-  "Why did the bicycle fall over? Because it was two tired!",
-]
-
-export default function JokeGenerator() {
-  const [customPrompt, setCustomPrompt] = useState("")
-  const [topic, setTopic] = useState("animals")
-  const [tone, setTone] = useState("silly")
-  const [jokeType, setJokeType] = useState("one-liner")
-  const [temperature, setTemperature] = useState([0.7])
-  const [joke, setJoke] = useState("")
-  const [loading, setLoading] = useState(false)
-  const [hasGenerated, setHasGenerated] = useState(false)
-
-  const generateJoke = () => {
-    setLoading(true)
-
-    // Simulate API call delay
-    setTimeout(() => {
-      // Get a random joke from our mock collection
-      const randomJoke = mockJokes[Math.floor(Math.random() * mockJokes.length)]
-
-      // Format the joke based on type
-      let formattedJoke = randomJoke
-      if (jokeType === "knock-knock") {
-        formattedJoke = `Knock knock!\nWho's there?\n${randomJoke.split(" ")[0]}.\n${randomJoke.split(" ")[0]} who?\n${randomJoke}`
-      }
-
-      setJoke(formattedJoke)
-      setLoading(false)
-      setHasGenerated(true)
-    }, 1500)
-  }
-
-  return (
-    <div className="grid gap-8 md:grid-cols-2">
-      <Card className="shadow-lg">
-        <CardHeader>
-          <CardTitle>Joke Parameters</CardTitle>
-          <CardDescription>Customize your joke preferences</CardDescription>
-        </CardHeader>
-        <CardContent className="space-y-6">
-          <div className="space-y-2">
-            <Label htmlFor="custom-prompt">Custom Prompt (Optional)</Label>
-            <Input
-              id="custom-prompt"
-              placeholder="Add specific details for your joke..."
-              value={customPrompt}
-              onChange={(e) => setCustomPrompt(e.target.value)}
-            />
-          </div>
-
-          <div className="space-y-2">
-            <Label htmlFor="topic">Topic</Label>
-            <Select value={topic} onValueChange={setTopic}>
-              <SelectTrigger id="topic">
-                <SelectValue placeholder="Select a topic" />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="work">Work</SelectItem>
-                <SelectItem value="people">People</SelectItem>
-                <SelectItem value="animals">Animals</SelectItem>
-                <SelectItem value="food">Food</SelectItem>
-                <SelectItem value="television">Television</SelectItem>
-                <SelectItem value="sports">Sports</SelectItem>
-                <SelectItem value="technology">Technology</SelectItem>
-              </SelectContent>
-            </Select>
-          </div>
-
-          <div className="space-y-2">
-            <Label htmlFor="tone">Tone</Label>
-            <Select value={tone} onValueChange={setTone}>
-              <SelectTrigger id="tone">
-                <SelectValue placeholder="Select a tone" />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="witty">Witty</SelectItem>
-                <SelectItem value="sarcastic">Sarcastic</SelectItem>
-                <SelectItem value="silly">Silly</SelectItem>
-                <SelectItem value="dark">Dark</SelectItem>
-                <SelectItem value="goofy">Goofy</SelectItem>
-                <SelectItem value="clean">Clean</SelectItem>
-              </SelectContent>
-            </Select>
-          </div>
-
-          <div className="space-y-2">
-            <Label htmlFor="joke-type">Joke Type</Label>
-            <Select value={jokeType} onValueChange={setJokeType}>
-              <SelectTrigger id="joke-type">
-                <SelectValue placeholder="Select a joke type" />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="pun">Pun</SelectItem>
-                <SelectItem value="knock-knock">Knock-knock</SelectItem>
-                <SelectItem value="one-liner">One-liner</SelectItem>
-                <SelectItem value="story">Story</SelectItem>
-                <SelectItem value="dad-joke">Dad Joke</SelectItem>
-              </SelectContent>
-            </Select>
-          </div>
-
-          <div className="space-y-4">
-            <div className="flex justify-between">
-              <Label htmlFor="temperature">Creativity Level</Label>
-              <span className="text-sm text-gray-500">{temperature[0].toFixed(1)}</span>
-            </div>
-            <Slider
-              id="temperature"
-              min={0.1}
-              max={1.0}
-              step={0.1}
-              value={temperature}
-              onValueChange={setTemperature}
-              className="w-full"
-            />
-            <div className="flex justify-between text-xs text-gray-500">
-              <span>Predictable</span>
-              <span>Creative</span>
-            </div>
-          </div>
-        </CardContent>
-        <CardFooter>
-          <Button
-            onClick={generateJoke}
-            className="w-full bg-purple-600 hover:bg-purple-700 text-white"
-            disabled={loading}
-          >
-            {loading ? (
-              <>
-                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
-                Generating...
-              </>
-            ) : (
-              "Generate Joke"
-            )}
-          </Button>
-        </CardFooter>
-      </Card>
-
-      <Card className="shadow-lg">
-        <CardHeader>
-          <CardTitle>Your Joke</CardTitle>
-          <CardDescription>{hasGenerated ? "Here's your custom joke!" : "Your joke will appear here"}</CardDescription>
-        </CardHeader>
-        <CardContent>
-          <div className="min-h-[200px] flex items-center justify-center">
-            {loading ? (
-              <div className="text-center">
-                <Loader2 className="h-8 w-8 animate-spin mx-auto text-purple-600" />
-                <p className="mt-2 text-gray-500">Crafting your joke...</p>
-              </div>
-            ) : hasGenerated ? (
-              <div className="bg-purple-50 dark:bg-gray-800 p-6 rounded-lg w-full">
-                <p className="text-lg font-medium whitespace-pre-line">{joke}</p>
-              </div>
-            ) : (
-              <p className="text-gray-400 text-center italic">
-                Adjust the parameters and click "Generate Joke" to see the result
-              </p>
-            )}
-          </div>
-        </CardContent>
-        {hasGenerated && (
-          <CardFooter className="flex justify-end">
-            <Button
-              variant="outline"
-              size="sm"
-              onClick={generateJoke}
-              disabled={loading}
-              className="flex items-center gap-1"
-            >
-              <RefreshCw className="h-4 w-4" />
-              Try Again
-            </Button>
-          </CardFooter>
-        )}
-      </Card>
-    </div>
-  )
-}
-
diff --git a/src/components/settings-card.tsx b/src/components/settings-card.tsx
new file mode 100644
index 0000000..d5d7f24
--- /dev/null
+++ b/src/components/settings-card.tsx
@@ -0,0 +1,42 @@
+import { Card } from '@/components/ui/card';
+import type { JokeSettings } from '@/types/settings';
+import { Settings as SettingsIcon } from 'lucide-react';
+
+interface SettingsCardProps {
+  settings: JokeSettings;
+}
+
+export function SettingsCard({ settings }: SettingsCardProps) {
+  if (!settings) return null;
+
+  const { topic, tone, jokeType, temperature } = settings;
+
+  return (
+    <Card className="p-6 bg-black/40 border border-amber-800/30 backdrop-blur-sm">
+      <div className="space-y-4">
+        <h2 className="text-lg font-semibold text-orange-400 flex items-center gap-2">
+          <SettingsIcon className="h-5 w-5" />
+          Current Settings
+        </h2>
+        <div className="grid grid-cols-2 gap-4">
+          <div className="space-y-1">
+            <div className="text-sm text-gray-400">Topic</div>
+            <div className="text-white font-medium capitalize">{topic}</div>
+          </div>
+          <div className="space-y-1">
+            <div className="text-sm text-gray-400">Tone</div>
+            <div className="text-white font-medium capitalize">{tone}</div>
+          </div>
+          <div className="space-y-1">
+            <div className="text-sm text-gray-400">Type</div>
+            <div className="text-white font-medium capitalize">{jokeType}</div>
+          </div>
+          <div className="space-y-1">
+            <div className="text-sm text-gray-400">Creativity</div>
+            <div className="text-white font-medium">{Math.round(temperature * 100)}%</div>
+          </div>
+        </div>
+      </div>
+    </Card>
+  );
+} 
\ No newline at end of file
diff --git a/src/components/simple-joke-display.tsx b/src/components/simple-joke-display.tsx
new file mode 100644
index 0000000..f1f1b05
--- /dev/null
+++ b/src/components/simple-joke-display.tsx
@@ -0,0 +1,133 @@
+"use client"
+
+import { useState } from 'react';
+import type { JokeSettings } from '@/types/settings';
+import { defaultSettings } from '@/types/settings';
+import { MessageCircle, Laugh, Sparkles, Tag, Settings } from 'lucide-react';
+import Link from 'next/link';
+
+interface SimpleJokeDisplayProps {
+  settings?: JokeSettings;
+}
+
+export default function SimpleJokeDisplay({ settings = defaultSettings }: SimpleJokeDisplayProps) {
+  const [isLoading, setIsLoading] = useState(false);
+  const [joke, setJoke] = useState('');
+
+  const generateJoke = async () => {
+
+      setIsLoading(true);
+      setJoke('');
+
+    try {
+      const response = await fetch('/api/joke', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          messages: [{ role: 'user', content: 'Tell me a joke' }],
+          parameters: settings,
+        }),
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to generate joke');
+      }
+
+      const reader = response.body?.getReader();
+      if (!reader) {
+        throw new Error('No reader available');
+      }
+
+      const decoder = new TextDecoder();
+      let jokeText = '';
+
+      while (true) {
+        const { done, value } = await reader.read();
+        if (done) break;
+
+        const text = decoder.decode(value);
+        const lines = text.split('\n').filter(line => line.trim() !== '');
+
+        for (const line of lines) {
+          if (line.startsWith('data: ')) {
+            const data = line.slice(6);
+            if (data === '[DONE]') continue;
+            const json = JSON.parse(data);
+            const content = json.choices[0]?.delta?.content;
+            if (content) {
+              jokeText += content;
+              setJoke(jokeText);
+            }
+          }
+        }
+      }
+    } catch (error) {
+      console.error('Error generating joke:', error);
+      setJoke('Failed to generate joke. Please try again.');
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return (
+    <div className="flex flex-col items-center justify-center min-h-[50vh] p-4">
+      <div className="text-9xl text-center animate-bounce mb-12">
+        😜
+      </div>
+      <button
+        onClick={generateJoke}
+        disabled={isLoading}
+        type="button"
+        className="w-full max-w-md text-2xl font-bold bg-orange-500 hover:bg-orange-600 text-white py-6 px-12 rounded-full mb-12 disabled:opacity-50 transition-all transform hover:scale-105 active:scale-95 shadow-lg hover:shadow-orange-500/25"
+      >
+        {isLoading ? (
+          <div className="flex items-center justify-center gap-3">
+            <svg className="animate-spin h-6 w-6" viewBox="0 0 24 24" aria-label="Loading">
+              <title>Loading...</title>
+              <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
+              <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
+            </svg>
+            Thinking ...
+          </div>
+        ) : (
+          'Tell me a joke!'
+        )}
+      </button>
+      {joke && (
+        <>
+          <div className="w-full max-w-lg text-center p-8 bg-black/40 border border-amber-800/30 rounded-3xl backdrop-blur-sm mb-4">
+            <p className="text-2xl text-white leading-relaxed">{joke}</p>
+          </div>
+          <div className="flex items-center gap-4 text-xs text-gray-400 bg-black/40 px-4 py-2 rounded-full border border-amber-800/30">
+            {settings.topic !== 'anything' && (
+              <div className="flex items-center gap-1.5">
+                <Tag className="h-3 w-3 text-orange-400" />
+                <span className="capitalize">{settings.topic}</span>
+              </div>
+            )}
+            <div className="flex items-center gap-1.5">
+              <Laugh className="h-3 w-3 text-orange-400" />
+              <span className="capitalize">{settings.tone}</span>
+            </div>
+            {settings.jokeType !== 'any' && (
+              <div className="flex items-center gap-1.5">
+                <MessageCircle className="h-3 w-3 text-orange-400" />
+                <span className="capitalize">{settings.jokeType}</span>
+              </div>
+            )}
+            <div className="flex items-center gap-1.5">
+              <Sparkles className="h-3 w-3 text-orange-400" />
+              <span>{Math.round(settings.temperature * 100)}% creative</span>
+            </div>
+            <Link href="/settings" className="flex items-center gap-1.5 text-orange-400 hover:text-orange-300 transition-colors ml-2">
+              <Settings className="h-3 w-3" />
+              <span>Edit</span>
+            </Link>
+          </div>
+        </>
+      )}
+    </div>
+  );
+} 
\ No newline at end of file
diff --git a/src/types/settings.ts b/src/types/settings.ts
new file mode 100644
index 0000000..b53bf0b
--- /dev/null
+++ b/src/types/settings.ts
@@ -0,0 +1,46 @@
+export interface JokeSettings {
+  topic: string;
+  tone: string;
+  jokeType: string;
+  temperature: number;
+}
+
+export const defaultSettings: JokeSettings = {
+  topic: 'anything',
+  tone: 'funny',
+  jokeType: 'any',
+  temperature: 0.7,
+};
+
+export const jokeTopics = [
+  'anything',
+  'animals',
+  'food',
+  'work',
+  'technology',
+  'sports',
+  'movies',
+  'music',
+  'science',
+  'history',
+];
+
+export const jokeTones = [
+  'funny',
+  'silly',
+  'witty',
+  'sarcastic',
+  'clever',
+  'goofy',
+  'punny',
+];
+
+export const jokeTypes = [
+  'any',
+  'one-liner',
+  'knock-knock',
+  'wordplay',
+  'story',
+  'pun',
+  'riddle',
+]; 
\ No newline at end of file


Reviews:
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:23:15Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:24:29Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:25:01Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:25:44Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:27:11Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:30:47Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:31:16Z
user: login: brolag
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-16T03:31:34Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:36:30Z
user: login: ArcTanSusan
body: Some human cleanup needed.
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-03-16T03:37:01Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:40:46Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:42:13Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:42:40Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:45:24Z
user: login: brolag
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-16T03:45:34Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:47:12Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:48:19Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:49:24Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:50:17Z
user: login: ArcTanSusan
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-16T03:51:42Z
user: login: brolag
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-16T04:14:48Z
user: login: brolag
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-16T04:15:11Z
user: login: ArcTanSusan
body: Loading settings icon appears forever. 
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-03-16T04:23:09Z

Review comments:
diff_hunk: @@ -1,22 +1,114 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
+  return (
+    typeof settings.topic === 'string' &&
+    typeof settings.tone === 'string' &&
+    typeof settings.jokeType === 'string' &&
+    typeof settings.temperature === 'number' &&
+    settings.temperature >= 0.1 &&
+    settings.temperature <= 1.0
+  )
+}
 
 export default function Home() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        if (!isValidSettings(parsed)) {
+          throw new Error('Invalid settings format')
+        }
path: src/app/page.tsx
user: login: ArcTanSusan
body: Very unlikely that the `try-catch` would error throw an error for all the code inside this statement. 
created_at: 2025-03-16T03:23:15Z
author_association: OWNER
diff_hunk: @@ -1,22 +1,114 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
path: src/app/page.tsx
user: login: ArcTanSusan
body: Time to use the power of Tyepscript for types!

```
const isValidSettings = (data: JokeSettings(
```

and then you could remove the checks `typeOf `in L17 & beloow. 
created_at: 2025-03-16T03:24:29Z
author_association: OWNER
diff_hunk: @@ -1,22 +1,114 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
+  return (
+    typeof settings.topic === 'string' &&
+    typeof settings.tone === 'string' &&
+    typeof settings.jokeType === 'string' &&
+    typeof settings.temperature === 'number' &&
+    settings.temperature >= 0.1 &&
+    settings.temperature <= 1.0
+  )
+}
 
 export default function Home() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        if (!isValidSettings(parsed)) {
+          throw new Error('Invalid settings format')
+        }
+        
+        // Merge with defaults to ensure all fields exist
+        const mergedSettings = {
+          topic: parsed.topic || defaultSettings.topic,
+          tone: parsed.tone || defaultSettings.tone,
+          jokeType: parsed.jokeType || defaultSettings.jokeType,
+          temperature: parsed.temperature ?? defaultSettings.temperature
+        }
+        
+        console.log('Loaded settings:', mergedSettings)
+        setSettings(mergedSettings)
+      } catch (error) {
+        console.error('Error loading settings:', error)
+        setError('Failed to load settings. Using defaults.')
+      } finally {
+        setIsLoading(false)
+      }
+    }
+
+    loadSettings()
+  }, [])
+
   return (
-    <main className="min-h-screen bg-gradient-to-b from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800 py-12 px-4 sm:px-6">
-      <div className="max-w-4xl mx-auto">
-        <div className="text-center mb-12">
-          <h1 className="text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white sm:text-5xl md:text-6xl">
-            <span className="block">Joke Generator</span>
-            <span className="block text-purple-600 dark:text-purple-400">Laugh on Demand</span>
-          </h1>
-          <p className="mt-3 max-w-md mx-auto text-base text-gray-500 dark:text-gray-400 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
-            Customize your joke parameters and get instant humor tailored just for you!
-          </p>
+    <div className="flex flex-col min-h-screen bg-gradient-to-b from-black to-amber-900/40">
+      {/* Header */}
+      <header className="p-4 flex justify-between items-center">
+        <h1 className="text-xl font-bold">
+          Tell Me A<span className="text-orange-500"> Joke</span>
+        </h1>
+        <Link href="/settings">
+          <Button variant="ghost" size="icon" className="text-gray-400 hover:text-white">
+            <Settings className="h-5 w-5" />
+            <span className="sr-only">Settings</span>
+          </Button>
+        </Link>
+      </header>
+
+      {/* Main content */}
+      <main className="flex-1 flex flex-col items-center justify-center p-6">
+        <div className="w-full max-w-xl mx-auto">
+          {isLoading ? (
+            <div className="flex items-center justify-center gap-2 text-gray-400">
+              <Loader2 className="h-5 w-5 animate-spin" />
+              <span>Loading settings...</span>
+            </div>
+          ) : (
+            <>
+              {error && (
+                <div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4 mb-4 flex items-center gap-2 text-red-400">
+                  <AlertCircle className="h-5 w-5 flex-shrink-0" />
+                  <p>{error}</p>
+                </div>
+              )}
+              <SimpleJokeDisplay settings={settings} />
+            </>
+          )}
         </div>
+      </main>
 
-        <JokeGenerator />
-      </div>
-    </main>
+      {/* Footer */}
+      <footer className="p-4 text-center text-sm text-gray-400">
+        <p>Powered by AI • Made with ❤️</p>
path: src/app/page.tsx
user: login: ArcTanSusan
body: good idea to credit AI.
created_at: 2025-03-16T03:25:01Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,228 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Slider } from "@/components/ui/slider"
+import { ArrowLeft, Loader2, AlertCircle } from "lucide-react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings, jokeTopics, jokeTones, jokeTypes } from "@/types/settings"
+import { SettingsCard } from "@/components/settings-card"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
+  return (
+    typeof settings.topic === 'string' &&
+    typeof settings.tone === 'string' &&
+    typeof settings.jokeType === 'string' &&
+    typeof settings.temperature === 'number' &&
+    settings.temperature >= 0.1 &&
+    settings.temperature <= 1.0
+  )
+}
+
+export default function Settings() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+  const [saved, setSaved] = useState(false)
+
+  // Load settings on mount
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        if (!isValidSettings(parsed)) {
+          throw new Error('Invalid settings format')
+        }
+        
+        // Merge with defaults to ensure all fields exist
+        const mergedSettings = {
+          topic: parsed.topic || defaultSettings.topic,
+          tone: parsed.tone || defaultSettings.tone,
+          jokeType: parsed.jokeType || defaultSettings.jokeType,
+          temperature: parsed.temperature ?? defaultSettings.temperature
+        }
+        
+        console.log('Loaded settings:', mergedSettings)
+        setSettings(mergedSettings)
+      } catch (error) {
+        console.error('Error loading settings:', error)
+        setError('Failed to load settings. Using defaults.')
+      } finally {
+        setIsLoading(false)
+      }
+    }
+
+    loadSettings()
+  }, [])
+
+  const saveSettings = () => {
+    setError(null)
+    try {
path: src/app/settings/page.tsx
user: login: ArcTanSusan
body: This code block would not actually ever throw an error. 
created_at: 2025-03-16T03:25:44Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,137 @@
+"use client"
+
+import { useState } from 'react';
+import type { JokeSettings } from '@/types/settings';
+import { defaultSettings } from '@/types/settings';
+import { MessageCircle, Laugh, Sparkles, Tag, Settings } from 'lucide-react';
+import Link from 'next/link';
+
+interface SimpleJokeDisplayProps {
+  settings?: JokeSettings;
+}
+
+export default function SimpleJokeDisplay({ settings = defaultSettings }: SimpleJokeDisplayProps) {
+  const [isLoading, setIsLoading] = useState(false);
+  const [joke, setJoke] = useState('');
+
+  const generateJoke = async () => {
+    try {
+      setIsLoading(true);
+      setJoke('');
+
+      const response = await fetch('/api/joke', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          messages: [{ role: 'user', content: 'Tell me a joke' }],
+          parameters: settings,
+        }),
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to generate joke');
+      }
+
+      const reader = response.body?.getReader();
+      if (!reader) {
+        throw new Error('No reader available');
+      }
+
+      const decoder = new TextDecoder();
+      let jokeText = '';
+
+      while (true) {
+        const { done, value } = await reader.read();
+        if (done) break;
+
+        const text = decoder.decode(value);
+        const lines = text.split('\n').filter(line => line.trim() !== '');
+
+        for (const line of lines) {
+          if (line.startsWith('data: ')) {
+            const data = line.slice(6);
+            if (data === '[DONE]') continue;
+
+            try {
path: src/components/simple-joke-display.tsx
user: login: ArcTanSusan
body: this won't ever throw an error. 
created_at: 2025-03-16T03:27:10Z
author_association: OWNER
diff_hunk: @@ -1,22 +1,114 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
path: src/app/page.tsx
user: login: ArcTanSusan
body: ```
const settings: JokeSettings
```
created_at: 2025-03-16T03:30:46Z
author_association: OWNER
diff_hunk: @@ -1,22 +1,114 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
+  return (
+    typeof settings.topic === 'string' &&
+    typeof settings.tone === 'string' &&
+    typeof settings.jokeType === 'string' &&
+    typeof settings.temperature === 'number' &&
+    settings.temperature >= 0.1 &&
+    settings.temperature <= 1.0
+  )
+}
 
 export default function Home() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
path: src/app/page.tsx
user: login: ArcTanSusan
body: this code doesn't throw an error, so no need to use `try-catch`. 
created_at: 2025-03-16T03:31:16Z
author_association: OWNER
diff_hunk: @@ -1,22 +1,114 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
+  return (
+    typeof settings.topic === 'string' &&
+    typeof settings.tone === 'string' &&
+    typeof settings.jokeType === 'string' &&
+    typeof settings.temperature === 'number' &&
+    settings.temperature >= 0.1 &&
+    settings.temperature <= 1.0
+  )
+}
 
 export default function Home() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        if (!isValidSettings(parsed)) {
+          throw new Error('Invalid settings format')
+        }
path: src/app/page.tsx
user: login: brolag
body: Won't hurt using it specially if we decide in the future to use an API. Wdt?
created_at: 2025-03-16T03:31:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,228 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Slider } from "@/components/ui/slider"
+import { ArrowLeft, Loader2, AlertCircle } from "lucide-react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings, jokeTopics, jokeTones, jokeTypes } from "@/types/settings"
+import { SettingsCard } from "@/components/settings-card"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
+  return (
+    typeof settings.topic === 'string' &&
path: src/app/settings/page.tsx
user: login: ArcTanSusan
body: same issue
created_at: 2025-03-16T03:36:30Z
author_association: OWNER
diff_hunk: @@ -1,22 +1,114 @@
-import JokeGenerator from "@/components/joke-generator"
+"use client"
+
+import Link from "next/link"
+import { Settings, Loader2, AlertCircle } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import SimpleJokeDisplay from "@/components/simple-joke-display"
+import { useState, useEffect } from "react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings } from "@/types/settings"
+
+// Type guard for settings validation
+const isValidSettings = (data: unknown): data is JokeSettings => {
+  if (!data || typeof data !== 'object') return false
+  
+  const settings = data as Partial<JokeSettings>
+  return (
+    typeof settings.topic === 'string' &&
+    typeof settings.tone === 'string' &&
+    typeof settings.jokeType === 'string' &&
+    typeof settings.temperature === 'number' &&
+    settings.temperature >= 0.1 &&
+    settings.temperature <= 1.0
+  )
+}
 
 export default function Home() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        if (!isValidSettings(parsed)) {
+          throw new Error('Invalid settings format')
+        }
path: src/app/page.tsx
user: login: ArcTanSusan
body: usually to get the settings from a global state, the react redux library can fetch this info or you can use a backend API. There's none of these options are being used for fetching settings, so this PR should not be anticipating a future API or redux global state.
created_at: 2025-03-16T03:40:46Z
author_association: OWNER
diff_hunk: @@ -39,3 +39,7 @@ yarn-error.log*
 # typescript
 *.tsbuildinfo
 next-env.d.ts
+
+# instructions
+instructions/
+instructions/*
path: .gitignore
user: login: ArcTanSusan
body: nitpick: seems that either L44 OR L45 is sufficient but not both.
created_at: 2025-03-16T03:42:13Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,71 @@
+import { Configuration, OpenAIApi } from 'openai-edge';
+
+// Create an OpenAI API client (that's edge-friendly!)
+const config = new Configuration({
+  apiKey: process.env.OPENAI_API_KEY,
+});
+const openai = new OpenAIApi(config);
+
+// Set response time limit
+export const maxDuration = 30;
path: src/app/api/joke/route.ts
user: login: ArcTanSusan
body: what unit of time? 30ms? Leave a comment.
created_at: 2025-03-16T03:42:40Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,132 @@
+"use client"
+
+import { useState } from 'react';
+import type { JokeSettings } from '@/types/settings';
+import { defaultSettings } from '@/types/settings';
+import { MessageCircle, Laugh, Sparkles, Tag, Settings } from 'lucide-react';
+import Link from 'next/link';
+
+interface SimpleJokeDisplayProps {
+  settings?: JokeSettings;
+}
+
+export default function SimpleJokeDisplay({ settings = defaultSettings }: SimpleJokeDisplayProps) {
+  const [isLoading, setIsLoading] = useState(false);
+  const [joke, setJoke] = useState('');
+
+  const generateJoke = async () => {
+    try {
+      setIsLoading(true);
+      setJoke('');
+
+      const response = await fetch('/api/joke', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          messages: [{ role: 'user', content: 'Tell me a joke' }],
+          parameters: settings,
+        }),
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to generate joke');
+      }
+
+      const reader = response.body?.getReader();
+      if (!reader) {
+        throw new Error('No reader available');
+      }
+
+      const decoder = new TextDecoder();
+      let jokeText = '';
+
+      while (true) {
+        const { done, value } = await reader.read();
+        if (done) break;
+
+        const text = decoder.decode(value);
+        const lines = text.split('\n').filter(line => line.trim() !== '');
+
+        for (const line of lines) {
+          if (line.startsWith('data: ')) {
+            const data = line.slice(6);
+            if (data === '[DONE]') continue;
+            const json = JSON.parse(data);
+            const content = json.choices[0]?.delta?.content;
+            if (content) {
path: src/components/simple-joke-display.tsx
user: login: ArcTanSusan
body: if content always exists anyway, then no need for `if`.
created_at: 2025-03-16T03:45:24Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,71 @@
+import { Configuration, OpenAIApi } from 'openai-edge';
+
+// Create an OpenAI API client (that's edge-friendly!)
+const config = new Configuration({
+  apiKey: process.env.OPENAI_API_KEY,
+});
+const openai = new OpenAIApi(config);
+
+// Set response time limit
+export const maxDuration = 30;
path: src/app/api/joke/route.ts
user: login: brolag
body: not required, removing it.
created_at: 2025-03-16T03:45:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,132 @@
+"use client"
+
+import { useState } from 'react';
+import type { JokeSettings } from '@/types/settings';
+import { defaultSettings } from '@/types/settings';
+import { MessageCircle, Laugh, Sparkles, Tag, Settings } from 'lucide-react';
+import Link from 'next/link';
+
+interface SimpleJokeDisplayProps {
+  settings?: JokeSettings;
+}
+
+export default function SimpleJokeDisplay({ settings = defaultSettings }: SimpleJokeDisplayProps) {
+  const [isLoading, setIsLoading] = useState(false);
+  const [joke, setJoke] = useState('');
+
+  const generateJoke = async () => {
+    try {
+      setIsLoading(true);
+      setJoke('');
+
+      const response = await fetch('/api/joke', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          messages: [{ role: 'user', content: 'Tell me a joke' }],
+          parameters: settings,
+        }),
+      });
+
path: src/components/simple-joke-display.tsx
user: login: ArcTanSusan
body: The `try` should only wrap around the API call, where the error is likely to happen.  then the `catch()` can end here if there are any errors in the API itself. 
created_at: 2025-03-16T03:47:12Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,46 @@
+export interface JokeSettings {
+  topic: string;
+  tone: string;
+  jokeType: string;
+  temperature: number;
+}
+
+export const defaultSettings: JokeSettings = {
+  topic: 'anything',
+  tone: 'funny',
+  jokeType: 'any',
+  temperature: 0.7,
+};
+
+export const jokeTopics = [
+  'anything',
+  'animals',
+  'food',
+  'work',
+  'technology',
+  'sports',
+  'movies',
+  'music',
+  'science',
+  'history',
+] as const;
path: src/types/settings.ts
user: login: ArcTanSusan
body: pretty sure that you don't need `as const`. Bad AI.
created_at: 2025-03-16T03:48:19Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,67 @@
+import { Card } from '@/components/ui/card';
+import type { JokeSettings } from '@/types/settings';
+import Link from 'next/link';
+import { Button } from './ui/button';
+import { Settings as SettingsIcon } from 'lucide-react';
+
+interface SettingsCardProps {
+  settings: JokeSettings;
+  isCompact?: boolean;
path: src/components/settings-card.tsx
user: login: ArcTanSusan
body: is this prop even used anywhere? if not, plz delete it.
created_at: 2025-03-16T03:49:24Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,205 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Slider } from "@/components/ui/slider"
+import { ArrowLeft, Loader2, AlertCircle } from "lucide-react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings, jokeTopics, jokeTones, jokeTypes } from "@/types/settings"
+import { SettingsCard } from "@/components/settings-card"
+
+
+export default function Settings() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+  const [saved, setSaved] = useState(false)
+
+  // Load settings on mount
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        // Merge with defaults to ensure all fields exist
+        const mergedSettings = {
+          topic: parsed.topic || defaultSettings.topic,
+          tone: parsed.tone || defaultSettings.tone,
+          jokeType: parsed.jokeType || defaultSettings.jokeType,
+          temperature: parsed.temperature ?? defaultSettings.temperature
+        }
+        
+        console.log('Loaded settings:', mergedSettings)
+        setSettings(mergedSettings)
+      } catch (error) {
+        console.error('Error loading settings:', error)
+        setError('Failed to load settings. Using defaults.')
+      } finally {
+        setIsLoading(false)
+      }
+    }
+
+    loadSettings()
+  }, [])
+
+  const saveSettings = () => {
+    setError(null)
+    localStorage.setItem('jokeSettings', JSON.stringify(settings))
+    console.log('Saved settings:', settings)
+    setSaved(true)
+    setTimeout(() => setSaved(false), 2000)
path: src/app/settings/page.tsx
user: login: ArcTanSusan
body: why a 2s delay? Unlikely that you'll need this fake time delay. 
created_at: 2025-03-16T03:50:16Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,205 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Slider } from "@/components/ui/slider"
+import { ArrowLeft, Loader2, AlertCircle } from "lucide-react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings, jokeTopics, jokeTones, jokeTypes } from "@/types/settings"
+import { SettingsCard } from "@/components/settings-card"
+
+
+export default function Settings() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+  const [saved, setSaved] = useState(false)
+
+  // Load settings on mount
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        // Merge with defaults to ensure all fields exist
+        const mergedSettings = {
+          topic: parsed.topic || defaultSettings.topic,
+          tone: parsed.tone || defaultSettings.tone,
+          jokeType: parsed.jokeType || defaultSettings.jokeType,
+          temperature: parsed.temperature ?? defaultSettings.temperature
+        }
+        
+        console.log('Loaded settings:', mergedSettings)
+        setSettings(mergedSettings)
+      } catch (error) {
+        console.error('Error loading settings:', error)
+        setError('Failed to load settings. Using defaults.')
+      } finally {
+        setIsLoading(false)
+      }
+    }
+
+    loadSettings()
+  }, [])
+
+  const saveSettings = () => {
+    setError(null)
+    localStorage.setItem('jokeSettings', JSON.stringify(settings))
+    console.log('Saved settings:', settings)
+    setSaved(true)
+    setTimeout(() => setSaved(false), 2000)
path: src/app/settings/page.tsx
user: login: ArcTanSusan
body: I dont think the assignment requires us to save settings so likely can be totally removed as a button.
created_at: 2025-03-16T03:51:42Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,205 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { Label } from "@/components/ui/label"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Slider } from "@/components/ui/slider"
+import { ArrowLeft, Loader2, AlertCircle } from "lucide-react"
+import type { JokeSettings } from "@/types/settings"
+import { defaultSettings, jokeTopics, jokeTones, jokeTypes } from "@/types/settings"
+import { SettingsCard } from "@/components/settings-card"
+
+
+export default function Settings() {
+  const [settings, setSettings] = useState<JokeSettings>(defaultSettings)
+  const [isLoading, setIsLoading] = useState(true)
+  const [error, setError] = useState<string | null>(null)
+  const [saved, setSaved] = useState(false)
+
+  // Load settings on mount
+  useEffect(() => {
+    const loadSettings = async () => {
+      setIsLoading(true)
+      setError(null)
+      
+      try {
+        const savedSettings = localStorage.getItem('jokeSettings')
+        if (!savedSettings) {
+          console.log('No saved settings found, using defaults')
+          return
+        }
+        
+        const parsed = JSON.parse(savedSettings)
+        
+        // Merge with defaults to ensure all fields exist
+        const mergedSettings = {
+          topic: parsed.topic || defaultSettings.topic,
+          tone: parsed.tone || defaultSettings.tone,
+          jokeType: parsed.jokeType || defaultSettings.jokeType,
+          temperature: parsed.temperature ?? defaultSettings.temperature
+        }
+        
+        console.log('Loaded settings:', mergedSettings)
+        setSettings(mergedSettings)
+      } catch (error) {
+        console.error('Error loading settings:', error)
+        setError('Failed to load settings. Using defaults.')
+      } finally {
+        setIsLoading(false)
+      }
+    }
+
+    loadSettings()
+  }, [])
+
+  const saveSettings = () => {
+    setError(null)
+    localStorage.setItem('jokeSettings', JSON.stringify(settings))
+    console.log('Saved settings:', settings)
+    setSaved(true)
+    setTimeout(() => setSaved(false), 2000)
path: src/app/settings/page.tsx
user: login: brolag
body: Is not part of othe assignment but it's useful. I'd rather keep it.
created_at: 2025-03-16T04:14:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,132 @@
+"use client"
+
+import { useState } from 'react';
+import type { JokeSettings } from '@/types/settings';
+import { defaultSettings } from '@/types/settings';
+import { MessageCircle, Laugh, Sparkles, Tag, Settings } from 'lucide-react';
+import Link from 'next/link';
+
+interface SimpleJokeDisplayProps {
+  settings?: JokeSettings;
+}
+
+export default function SimpleJokeDisplay({ settings = defaultSettings }: SimpleJokeDisplayProps) {
+  const [isLoading, setIsLoading] = useState(false);
+  const [joke, setJoke] = useState('');
+
+  const generateJoke = async () => {
+    try {
+      setIsLoading(true);
+      setJoke('');
+
+      const response = await fetch('/api/joke', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          messages: [{ role: 'user', content: 'Tell me a joke' }],
+          parameters: settings,
+        }),
+      });
+
path: src/components/simple-joke-display.tsx
user: login: brolag
body: Will keep this structure for now. 
created_at: 2025-03-16T04:15:11Z
author_association: COLLABORATOR

PR comments:
user: login: ArcTanSusan
created_at: 2025-03-16T04:20:34Z
body: I'm stuck on the loading page: Do you see this page ?

![Screenshot 2025-03-15 at 11 20 06 PM](https://github.com/user-attachments/assets/3bb1b536-2dbd-49e2-af05-84e2b086bc12)

author_association: OWNER
user: login: ArcTanSusan
created_at: 2025-03-16T04:21:59Z
body: Why are there 2 settings pages? Duplicated? Did you forget to delete 1?

<img width="341" alt="Screenshot 2025-03-15 at 11 21 30 PM" src="https://github.com/user-attachments/assets/10ab34c4-45ad-425d-a806-aeca019f7a78" />

author_association: OWNER
user: login: brolag
created_at: 2025-03-16T04:25:28Z
body: Those are different settings components @ArcTanSusan. It should work now.
author_association: COLLABORATOR
user: login: bshambaugh
created_at: 2025-03-16T16:53:26Z
body: ![Screenshot_2025-03-16_10-54-12](https://github.com/user-attachments/assets/c894e830-8b05-42b6-a889-de94bc7f38f9)
![Screenshot_2025-03-16_10-02-05](https://github.com/user-attachments/assets/a2b67a97-7796-441e-9850-afcc3a5dacf0)
Is the field to add a custom prompt needed?
author_association: COLLABORATOR