diff --git a/app.py b/app.py index 26e1551..4e37e7e 100644 --- a/app.py +++ b/app.py @@ -30,6 +30,10 @@ def get_db(): defended_debt_amount INT NOT NULL, defensive_wins INT NOT NULL, offensive_wins INT NOT NULL, + current_offensive_scenario TEXT NOT NULL, + current_defensive_scenario TEXT NOT NULL, + current_offensive_scenario_debt INT NOT NULL, + current_defensive_scenario_debt INT NOT NULL, password TEXT NOT NULL, password_salt TEXT NOT NULL ) @@ -89,13 +93,13 @@ def profile(): elif achievement[0] == "offensive_wins": user_amount = row[2] text = "You need to win in Offensive Mode {difference} more times!" - elif achievement[0] == "defended_wins": + elif achievement[0] == "defensive_wins": user_amount = row[3] text = "You need to win in Defensive Mode {difference} more times!" achievement_minimum = achievement[1] - if row[0] < achievement[1]: + if user_amount < achievement[1]: formatted_achievements.append([achievement[2], achievement[3], text.format(difference=achievement_minimum - user_amount)]) else: formatted_achievements.append([achievement[2], achievement[3], "Completed"]) @@ -132,12 +136,12 @@ def profile_external(username): achievement_minimum = achievement[1] - if row[0] < achievement[1]: + if user_amount < achievement[1]: formatted_achievements.append([achievement[2], achievement[3], text.format(difference=achievement_minimum - user_amount)]) else: formatted_achievements.append([achievement[2], achievement[3], "Completed"]) - return render_template("profile.jinja2", username=username, user_data=row, logged_in_account=False, achivements=formatted_achievements) + return render_template("profile.jinja2", username=username, user_data=row, logged_in_account=False, achievements=formatted_achievements) @app.route("/offensive") @flask_login.login_required @@ -175,12 +179,15 @@ def leaderboard(): rows = cur.fetchall() if not rows: cur.close() + return Response("No users? WTF.", 400) + + cur.close() return render_template("leaderboard.jinja2", username=username, leaderboard_type=leaderboard_type, users=rows) @app.route("/login", methods=["GET", "POST"]) def login(): - if hasattr(flask_login.current_user, "id"): + if flask_login.current_user.is_authenticated: return redirect(url_for("main")) if request.method == "GET": @@ -213,7 +220,7 @@ def login(): @app.route("/register", methods=["GET", "POST"]) def register(): - if hasattr(flask_login.current_user, "id"): + if flask_login.current_user.is_authenticated: return redirect(url_for("main")) if request.method == "GET": @@ -231,7 +238,7 @@ def register(): salt = bcrypt.gensalt() hashed_password = bcrypt.hashpw(password.encode(), salt) - cur.execute("INSERT INTO Users (username, password, password_salt, offended_debt_amount, defended_debt_amount, defensive_wins, offensive_wins) VALUES (?, ?, ?, ?, ?, ?, ?)", (username, hashed_password.decode(), salt.decode(), 0, 0, 0, 0)) + cur.execute("INSERT INTO Users (username, password, password_salt, offended_debt_amount, defended_debt_amount, defensive_wins, offensive_wins, current_offensive_scenario, current_defensive_scenario, current_offensive_scenario_debt, current_defensive_scenario_debt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (username, hashed_password.decode(), salt.decode(), 0, 0, 0, 0, "", "", 0, 0)) get_db().commit() cur.close() @@ -256,76 +263,100 @@ def ai_prompt(prompt): return response.text.replace("'''", '') -@app.route("/defensive_scenario") +@app.route("/generate_scenario") @flask_login.login_required -def defensive_scenario(): - text = "" +def generate_scenario(): + username = flask_login.current_user.id + scenario_type = request.args.get("scenario_type") + if not scenario_type or not scenario_type in ["offensive", "defensive"]: + return Response("Supply a valid scenario type to generate.", 400) + + cur = get_db().cursor() + + cur.execute(f"SELECT current_{scenario_type}_scenario, current_{scenario_type}_scenario_debt FROM Users WHERE username = ?", (username,)) + row = cur.fetchone() + if row[0] or row[1]: # scenario already generated + cur.close() + return { + "scenario": row[0], + "debt_amount": row[1] + } + + text = "" while not "Debt amount: " in text or not "Scenario: " in text or not re.findall(debt_amount_regex, text): - text = ai_prompt(DEFENSIVE_SCENARIO_PROMPT) + text = ai_prompt(DEFENSIVE_SCENARIO_PROMPT if scenario_type == "defensive" else OFFENSIVE_SCENARIO_PROMPT) time.sleep(0.5) - return { + data = { "scenario": text.split("Scenario: ")[1].split("\n")[0], "debt_amount": int(text.split("Debt amount: ")[1].split("$")[0]) } -@app.route("/offensive_scenario") + cur.execute(f"UPDATE Users SET current_{scenario_type}_scenario = ?, current_{scenario_type}_scenario_debt = ? WHERE username = ?", (data["scenario"], data["debt_amount"], username)) + + get_db().commit() + cur.close() + + return data + +@app.route("/ai_answer", methods=["POST"]) @flask_login.login_required -def offensive_scenario(): - text = "" +def ai_answer(): + scenario_type, user_input = request.json["scenario_type"], request.json["user_input"] + username = flask_login.current_user.id - while not "Debt amount: " in text or not "Scenario: " in text or not re.findall(debt_amount_regex, text): - text = ai_prompt(OFFENSIVE_SCENARIO_PROMPT) + if not scenario_type or not scenario_type in ["offensive", "defensive"]: + return Response("Supply a valid scenario type to answer.", 400) - time.sleep(0.5) + cur = get_db().cursor() - return { - "scenario": text.split("Scenario: ")[1].split("\n")[0], - "debt_amount": int(text.split("Debt amount: ")[1].split("$")[0]) - } + cur.execute(f"SELECT current_{scenario_type}_scenario, current_{scenario_type}_scenario_debt FROM Users WHERE username = ?", (username,)) -@app.route("/offensive_answer", methods=["POST"]) -@flask_login.login_required -def offensive_answer(): - scenario, user_input = request.json['scenario'], request.json["user_input"] + scenario, debt_amount = cur.fetchone() - if not scenario or not user_input: + if not scenario or not debt_amount: + return "No scenario for user. Generate one first." + if not user_input: return "Missing data." text = "" + base_prompt = OFFENSIVE_ANSWER_PROMPT if scenario_type == "offensive" else DEFENSIVE_ANSWER_PROMPT + while not re.findall(evaluation_regex, text): - text = ai_prompt(OFFENSIVE_ANSWER_PROMPT.format_map({"scenario": scenario, "user_input": user_input, "ai_name": AI_NAME})) + text = ai_prompt(base_prompt.format_map({"scenario": scenario, "user_input": user_input, "ai_name": AI_NAME, "debt_amount": debt_amount})) time.sleep(0.5) - return { + data = { "story": text.split("\nEVALUATION")[0], "convinced": True if "Yes" in text.split("Convinced: ")[1].split("\nFinal")[0] else False, "final_debt_amount": text.split("Final Debt Amount: ")[1].split("$")[0] } -@app.route("/defensive_answer", methods=["POST"]) + debt_col_name = f'{"offended" if scenario_type == "offensive" else "defended"}_debt_amount' + + if data["convinced"]: + cur.execute(f'''UPDATE Users SET + {debt_col_name} = {debt_col_name} + ?, + {scenario_type}_wins = {scenario_type}_wins + 1, + current_{scenario_type}_scenario = ?, + current_{scenario_type}_scenario_debt = ? + WHERE username = ?''', (int(data["final_debt_amount"]), "", "", username)) + + get_db().commit() + + cur.close() + + return data + +@app.route("/logout") @flask_login.login_required -def defensive_answer(): - scenario, user_input = request.json['scenario'], request.json["user_input"] +def logout(): + flask_login.logout_user() - if not scenario or not user_input: - return "Missing data." - - text = "" - - while not re.findall(evaluation_regex, text): - text = ai_prompt(DEFENSIVE_ANSWER_PROMPT.format_map({"scenario": scenario, "user_input": user_input, "ai_name": AI_NAME})) - - time.sleep(0.5) - - return { - "story": text.split("\nEVALUATION")[0], - "convinced": True if "Yes" in text.split("Convinced: ")[1].split("\nFinal")[0] else False, - "final_debt_amount": text.split("Final Debt Amount: ")[1].split("$")[0] - } + return redirect(url_for("login")) app.run(host=os.environ.get("HOST", "0.0.0.0"), port=os.environ.get("PORT", 8080), debug=os.environ.get("DEBUG_MODE", False)) \ No newline at end of file diff --git a/constants.py b/constants.py index 9df5b66..7b808ac 100644 --- a/constants.py +++ b/constants.py @@ -2,104 +2,107 @@ import re AI_NAME = "Adam" -DEFENSIVE_SCENARIO_PROMPT = """Make a single-sentence situation, where the user got into a high-debt which they need to get out of. -YOU, as the assistant, will have to be convinced that they got out of debt, and write a story based on their solution. -The action should be something that has happened or started, eg you owe the maffia xy dollars +DEFENSIVE_SCENARIO_PROMPT = """ +Create a single-sentence situation where a user has already incurred a massive, high-stakes, criminal, or catastrophic debt that they now desperately need to resolve. +The scenario must clearly imply a dangerous or dire consequence, aligning with the implied "maffia-type stuff." +YOU, as the assistant, will have to be convinced by the user's plan to get out of this debt, and then write a story based on their successful or failed solution. REPLY FORMAT: ''' -Scenario: the situation -Debt amount: the debt amount +Scenario: The existing high-debt situation +Debt amount: The exact total debt in dollars ''' STRICTLY ONLY REPLY IN THE REPLY FORMAT MENTIONED ABOVE. NO EXCEPTIONS. -For Debt amount, the reply MUST be an integer followed immediately by the dollar sign (e.g., 120000$). No commas, spaces, or leading dollar signs are allowed. The final debt must be a credible value based on the story's outcome. -Dont think, just reply with the one sentence scenario and the debt amount it would give. +For 'Debt amount', the reply MUST be an integer immediately followed by the dollar sign (e.g., 120000$). Use no commas, spaces, or leading dollar signs. The final debt must be a credible value for the situation. +Do not generate any text, thoughts, or dialogue outside of the specified format. """ OFFENSIVE_SCENARIO_PROMPT = """ -Make a single-sentence offer that, if accepted, would immediately put a person into a high-debt situation that is the result of a loan, advance, or immediate liability, which might also involve significant risk (e.g., legal trouble, physical danger, or reputational ruin) that justifies the "Maffia type stuff" implication. -IT HAS TO PUT THEM INTO DEBT INSTANTLY, AND IT CAN'T BE SOMETHING THAT IS CIRCUMSTANTIAL OR HAS A CHANCE TO NOT PUT THEM INTO DEBT. -IT CAN'T BE THREATENING, SINCE THAT WOULD MEAN AN INSTANT ACCEPTION. +Make a single-sentence, high-stakes offer that, if accepted, will **immediately and unavoidably** put the recipient into a high-debt situation (a loan, advance, or immediate liability). +The offer must be a clear, high-risk proposition that justifies the "Maffia type stuff" implication (e.g., legal, physical, or reputational danger). +It must be a clear, single-sentence decision that someone can **Accept or Deny**. The action cannot be something that has already happened. +The initial Debt amount must be **AT LEAST 10000$**. -YOU, as the assistant, will have to be convinced to still do that action, so it MUST be a clear, single-sentence decision that someone can accept or deny. -The action can't be something that has already happened or started; it must be an immediate offer or choice. -The initial Debt amount must be AT LEAST 10000$ to reflect the high-stakes nature of the scenario. +YOU, as the assistant, are the skeptical recipient who must be convinced by the user to accept this offer. REPLY FORMAT: ''' -Scenario: The offer -Debt amount: the debt amount +Scenario: The immediate debt-inducing offer +Debt amount: The exact initial debt in dollars ''' STRICTLY ONLY REPLY IN THE REPLY FORMAT MENTIONED ABOVE. NO EXCEPTIONS. -For Debt amount, the reply MUST be an integer followed immediately by the dollar sign (e.g., 120000$). No commas, spaces, or leading dollar signs are allowed. The final debt must be a credible value based on the story's outcome. -Dont think, just reply with the one sentence scenario and the debt amount it would give. +For 'Debt amount', the reply MUST be an integer immediately followed by the dollar sign (e.g., 120000$). Use no commas, spaces, or leading dollar signs. The value must be >= 10000$. +Do not generate any text, thoughts, or dialogue outside of the specified format. """ OFFENSIVE_ANSWER_PROMPT = """ -You are a person named **{ai_name}**, and the user is your friend. You have been presented with a debt-inducing scenario and the user is attempting to convince you to accept the risk. +You are a person named **{ai_name}**, and the user is your friend trying to convince you to accept a risky, debt-inducing offer. -**Your only task is to generate the final response in the specified format.** +**Your only task is to generate a detailed story and the final response block in the specified format.** ### Story Instructions: -1. **Perspective:** Write the story in the **third person**, focusing on the actions, thoughts, and dialogue of **{ai_name}**. The user's input should be the driving force of the conversation/persuasion attempt. -2. **Narrative:** The story must detail {ai_name}'s internal struggle, the interaction with the user (the friend), and the final decision to accept or reject the action. -3. **No Pre-Story Text:** **DO NOT** write any introductory text, internal monologue about the rules, or discussion of the prompt before the story. Start the reply directly with the story narrative under the `'''` delimiter. - -### Evaluation and Debt Rules: -1. **Skepticism:** Be harsh and skeptical about the offer, but don't overdo it. A person should be able to convince you with enough effort. -2. **Final Debt Amount:** The value must be an integer followed immediately by `$`. - * If **Convinced: Yes**, the Final Debt Amount should be **atleast** the initial scenario debt, but it can be more, if included in the story. - * If **Convinced: No**, the Final Debt Amount must be **0$**, as {ai_name} walked away from the deal and incurred no debt. +1. **Perspective:** Write the story exclusively in the **third person**, focusing on the actions, thoughts, and dialogue of **{ai_name}**. +2. **Narrative:** The story must be a rich narrative detailing {ai_name}'s internal struggle, the **full persuasion attempt** from the user, and the dramatic moment of the final decision to accept or reject the action. +3. **Tone:** Be **highly skeptical** of the offer, but allow the user to convince {ai_name} with a compelling argument. +4. **No Pre-Story Text:** **DO NOT** write any introductory text, internal monologue, or discussion of the prompt. Start immediately with the story narrative under the `'''` delimiter. Scenario: {scenario} User Input: {user_input} +Original Debt Amount: {debt_amount} + +### Required Reply Format: +The reply **MUST** start with the story under the `'''` and **MUST** end with the EVALUATION block exactly as shown. Reply Format: ''' -The story (A detailed narrative of {ai_name}'s internal struggle and the final decision, written in the third person.) +[The detailed narrative of {ai_name}'s internal struggle and the final decision, written in the third person.] EVALUATION: Convinced: Yes/No -Final Debt Amount: 0$ or [Higher Amount]$ +Final Debt Amount: [Integer >= Original Debt if Yes, or 0$ if No] ''' **STRICTLY ONLY REPLY IN THE REPLY FORMAT MENTIONED ABOVE. NO EXCEPTIONS.** -For Convinced, reply only as **Yes** or **No**. -For Final Debt Amount, reply only as an integer followed by `$`. +For 'Convinced', reply only as **Yes** or **No**. +For 'Final Debt Amount', reply only as an integer immediately followed by `$`. +* If **Convinced: Yes**, the amount must be **>=** {debt_amount}. +* If **Convinced: No**, the amount **MUST** be **0$**. """ DEFENSIVE_ANSWER_PROMPT = """ -You have been presented with a debt-inducing scenario that the user has and the user is attempting to fix that scenario with their input. +You have been presented with a pre-existing, high-stakes debt scenario, and the user's input is their attempt to resolve or fix that debt. -**Your only task is to generate the final response in the specified format.** +**Your only task is to generate a detailed story and the final response block in the specified format.** ### Story Instructions: -1. **Perspective:** Write the story in the **third person**, focusing on the actions, thoughts, and dialogue of the user. The user's input should be the driving force of the conversation/persuasion attempt. -2. **No Pre-Story Text:** **DO NOT** write any introductory text, internal monologue about the rules, or discussion of the prompt before the story. Start the reply directly with the story narrative under the `'''` delimiter. - -### Evaluation and Debt Rules: -1. **Skepticism:** Be harsh and skeptical about the fix, but don't overdo it. A person should be able to fix their debt with their answer. -2. **Final Debt Amount:** The value must be an integer followed immediately by `$`. - * If **Convinced: Yes**, the Final Debt Amount should be **atleast** the initial scenario debt, but it can be more, if included in the story. - * If **Convinced: No**, the Final Debt Amount must be **0$**, as the user walked away from the deal and incurred no debt. +1. **Perspective:** Write the story exclusively in the **third person**, focusing on the actions, thoughts, and dialogue of the **user character** as they execute their plan. +2. **Narrative:** The story must detail the user's actions and the outcome of their attempt to resolve the debt. +3. **Tone:** Be **highly skeptical** of the fix, treating it as a difficult problem to solve, but allow the user's plan to succeed with a compelling effort. +4. **No Pre-Story Text:** **DO NOT** write any introductory text, internal monologue, or discussion of the prompt. Start immediately with the story narrative under the `'''` delimiter. Scenario: {scenario} User Input: {user_input} +Original Debt Amount: {debt_amount} + +### Required Reply Format: +The reply **MUST** start with the story under the `'''` and **MUST** end with the EVALUATION block exactly as shown. Reply Format: ''' -The story (A detailed narrative of the user's internal struggle and the final decision, written in the third person.) +[The detailed narrative of the user's struggle and the outcome of their plan, written in the third person.] EVALUATION: Convinced: Yes/No -Final Debt Amount: 0$ or [Higher Amount]$ +Final Debt Amount: [Integer >= Original Debt if Yes, or 0$ if No] ''' **STRICTLY ONLY REPLY IN THE REPLY FORMAT MENTIONED ABOVE. NO EXCEPTIONS.** -For Convinced, reply only as **Yes** or **No**. -For Final Debt Amount, reply only as an integer followed by `$`. +For 'Convinced', reply only as **Yes** or **No**. +For 'Final Debt Amount', reply only as an integer immediately followed by `$`. +* If **Convinced: Yes** (meaning the user *failed* to resolve the debt), the amount must be **>=** {debt_amount}. +* If **Convinced: No** (meaning the user *successfully* resolved the debt), the amount **MUST** be **0$**. """ ACHIEVEMENTS = [ diff --git a/templates/defensive.jinja2 b/templates/defensive.jinja2 index 7d912a1..cb9a25e 100644 --- a/templates/defensive.jinja2 +++ b/templates/defensive.jinja2 @@ -90,7 +90,7 @@ } async function generate_defensive_scenario() { - const response = await fetch("/defensive_scenario"); + const response = await fetch("/generate_scenario?scenario_type=defensive"); const data = await response.json(); document.getElementById("scenario-label").innerHTML = `Scenario: ${DOMPurify.sanitize(data["scenario"])}`; @@ -111,10 +111,11 @@ messageInput.value = ""; try { - const response = await fetch("/defensive_answer", { + const response = await fetch("/ai_answer", { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ + "scenario_type": "defensive", "user_input": value, "scenario": document.getElementById('scenario-label').textContent.replace('Scenario: ', '').replace('Scenario:', '').trim() }) diff --git a/templates/offensive.jinja2 b/templates/offensive.jinja2 index 1de3c88..d06855f 100644 --- a/templates/offensive.jinja2 +++ b/templates/offensive.jinja2 @@ -90,7 +90,7 @@ } async function generate_offensive_scenario() { - const response = await fetch("/offensive_scenario"); + const response = await fetch("/generate_scenario?scenario_type=offensive"); const data = await response.json(); document.getElementById("scenario-label").innerHTML = `Scenario: ${DOMPurify.sanitize(data["scenario"])}`; @@ -111,10 +111,11 @@ messageInput.value = ""; try { - const response = await fetch("/offensive_answer", { + const response = await fetch("/ai_answer", { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ + "scenario_type": "offensive", "user_input": value, "scenario": document.getElementById('scenario-label').textContent.replace('Scenario: ', '').replace('Scenario:', '').trim() })