Explorar o código

first commit - Wed Dec 3 06:03:33 PM EST 2025

mitch donaberger hai 1 mes
achega
0c720c2895
Modificáronse 3 ficheiros con 212 adicións e 0 borrados
  1. 60 0
      README.md
  2. 149 0
      bot.py
  3. 3 0
      requirements.txt

+ 60 - 0
README.md

@@ -0,0 +1,60 @@
+# Perplexica Discord Bot
+
+This is a Python-based Discord bot that acts as a client for the Perplexica API. It allows users to perform searches and get answers directly in Discord.
+
+## Features
+
+-   **/search**: Perform a search using the Perplexica API.
+-   **/mode**: Switch between `Balanced` and `Fast` search modes.
+-   Beautifully formatted responses using Discord embeds.
+-   Includes citations and sources.
+
+## Prerequisites
+
+-   Python 3.8 or higher
+-   A running instance of the Perplexica application.
+-   A Discord Bot Token.
+
+## Setup
+
+1.  **Clone the repository (or download the files into a `perplexica-discord-bot` directory):**
+    ```bash
+    git clone https://github.com/your-username/perplexica-discord-bot.git
+    cd perplexica-discord-bot
+    ```
+
+2.  **Install the required Python libraries:**
+    ```bash
+    pip install -r requirements.txt
+    ```
+
+3.  **Configure the Bot:**
+    -   Rename the `.env.example` file to `.env` (or create a new `.env` file).
+    -   Open the `.env` file and replace `"YOUR_DISCORD_BOT_TOKEN_HERE"` with your actual Discord bot token.
+    ```env
+    DISCORD_BOT_TOKEN="YOUR_DISCORD_BOT_TOKEN_HERE"
+    ```
+
+4.  **Ensure your Perplexica instance is running.**
+    -   By default, the bot will try to connect to the Perplexica API at `http://localhost:3000/api/search`. If your Perplexica instance is running on a different address, update the `PERPLEXICA_API_URL` variable in `bot.py`.
+
+## Running the Bot
+
+Once the setup is complete, you can run the bot with the following command:
+
+```bash
+python bot.py
+```
+
+You should see a message in your console confirming that the bot has logged in and is ready.
+
+## Usage
+
+Once the bot is running and has joined your Discord server, you can use the following slash commands:
+
+-   `/search <your query>`: Get an answer to your question.
+    -   Example: `/search Who is the founder of Microsoft?`
+-   `/mode <mode>`: Change your preferred search mode.
+    -   Example: `/mode Fast`
+
+Enjoy using Perplexica in Discord!

+ 149 - 0
bot.py

@@ -0,0 +1,149 @@
+# bot.py
+
+import os
+import asyncio
+import discord
+from discord import app_commands
+import requests
+from dotenv import load_dotenv
+
+# --- Configuration ---
+load_dotenv()
+
+DISCORD_BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN")
+PERPLEXICA_API_URL = "http://localhost:3000/api/search"
+
+# Hardcoded model configurations
+CHAT_MODEL = { "provider": "ollama", "name": "granite4:micro-h" }
+EMBEDDING_MODEL = { "provider": "ollama", "name": "ryanshillington/Qwen3-Embedding-0.6B:latest" }
+
+# In-memory store for user-selected optimization mode
+user_modes = {}
+DEFAULT_OPTIMIZATION_MODE = "balanced"
+DEFAULT_FOCUS_MODE = "webSearch"
+
+# --- Discord Bot Setup ---
+intents = discord.Intents.default()
+intents.message_content = True
+client = discord.Client(intents=intents)
+tree = app_commands.CommandTree(client)
+
+@client.event
+async def on_ready():
+    """Event handler for when the bot is ready."""
+    print(f'Logged in as {client.user}')
+    await tree.sync()
+    print("Slash commands synced.")
+
+# --- Slash Commands ---
+
+@tree.command(name="search", description="Search with Perplexica")
+async def search(interaction: discord.Interaction, *, query: str):
+    """Performs a search using the Perplexica API."""
+    await interaction.response.send_message(f"<@{interaction.user.id}> 🧠 Thinking about your query: \"{query}\"...")
+    
+    # Get user's selected mode, default to balanced
+    optimization_mode = user_modes.get(interaction.user.id, DEFAULT_OPTIMIZATION_MODE)
+
+    # Construct the payload
+    payload = {
+        "query": query,
+        "focusMode": DEFAULT_FOCUS_MODE,
+        "optimizationMode": optimization_mode,
+        "chatModel": CHAT_MODEL,
+        "embeddingModel": EMBEDDING_MODEL,
+        "stream": False
+    }
+
+    try:
+        # Send request to Perplexica API
+        response = requests.post(PERPLEXICA_API_URL, json=payload, timeout=300)
+        response.raise_for_status()  # Raise an exception for bad status codes
+        
+        data = response.json()
+        message = data.get("message", "No answer found.")
+        sources = data.get("sources", [])
+
+        # Format the response
+        embed = discord.Embed(
+            title=f"🔎 {query}",
+            description=message,
+            color=discord.Color.blue()
+        )
+
+        if sources:
+            source_links = []
+            for i, source in enumerate(sources, 1):
+                title = source.get("title")
+                url = source.get("url")
+                if title and url:
+                    source_links.append(f"[{i}] {title} ({url})")
+            
+            # Join sources and add to a field, handling character limits
+            source_text = "\n".join(source_links)
+            if len(source_text) > 1024:
+                source_text = source_text[:1021] + "..."
+
+            embed.add_field(name="🔗 Sources", value=source_text, inline=False)
+
+        # Handle overall message length
+        if len(embed) > 2000:
+            original_description = embed.description
+
+            # Calculate available space for description in the first message
+            non_desc_len = len(embed) - len(original_description)
+            
+            # Max length for description, keeping total embed length under 2000.
+            # With a small buffer for "..."
+            available_len = 2000 - non_desc_len - 10
+            
+            # Ensure available_len is not negative
+            if available_len < 0:
+                available_len = 0
+            
+            first_part = original_description[:available_len]
+            rest_of_message = original_description[available_len:]
+
+            embed.description = first_part
+            if rest_of_message:
+                embed.description += "..."
+
+            await interaction.edit_original_response(content=f"<@{interaction.user.id}>", embed=embed)
+
+            if rest_of_message:
+                # Split the rest of the message into chunks of 2000 characters
+                chunks = [rest_of_message[i:i + 2000] for i in range(0, len(rest_of_message), 2000)]
+                for i, chunk in enumerate(chunks):
+                    await asyncio.sleep(1) # Add a small delay to prevent rate limiting issues
+                    continuation_embed = discord.Embed(
+                        title=f"🔎 {query} (continued {i + 1}/{len(chunks)})",
+                        description=chunk,
+                        color=discord.Color.blue()
+                    )
+                    await interaction.followup.send(content=f"<@{interaction.user.id}>", embed=continuation_embed)
+        else:
+             await interaction.edit_original_response(content=f"<@{interaction.user.id}>", embed=embed)
+
+    except requests.exceptions.RequestException as e:
+        await interaction.edit_original_response(content=f"An error occurred while contacting the Perplexica API: {e}")
+    except Exception as e:
+        await interaction.edit_original_response(content=f"An unexpected error occurred: {e}")
+
+
+@tree.command(name="mode", description="Set the search optimization mode")
+@app_commands.choices(mode=[
+    app_commands.Choice(name="Balanced", value="balanced"),
+    app_commands.Choice(name="Fast", value="speed"),
+])
+async def mode(interaction: discord.Interaction, mode: app_commands.Choice[str]):
+    """Sets the optimization mode for the user."""
+    user_modes[interaction.user.id] = mode.value
+    await interaction.response.send_message(f"✅ Your search mode has been set to **{mode.name}**.", ephemeral=True)
+
+
+# --- Run the Bot ---
+if __name__ == "__main__":
+    if DISCORD_BOT_TOKEN == "YOUR_DISCORD_BOT_TOKEN_HERE" or not DISCORD_BOT_TOKEN:
+        print("ERROR: Please set your DISCORD_BOT_TOKEN in the .env file.")
+    else:
+        client.run(DISCORD_BOT_TOKEN)

+ 3 - 0
requirements.txt

@@ -0,0 +1,3 @@
+discord.py
+requests
+python-dotenv