Skip to content
This repository has been archived by the owner on Jun 12, 2024. It is now read-only.

Commit

Permalink
Add public AIs and knowledge and remove demo mode
Browse files Browse the repository at this point in the history
  • Loading branch information
janpawellek committed Sep 15, 2023
1 parent 1e72274 commit 9e3bfc3
Show file tree
Hide file tree
Showing 21 changed files with 258 additions and 286 deletions.
93 changes: 64 additions & 29 deletions backaind/ainteraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
from datetime import datetime
import json

from flask import Blueprint, render_template, session
from flask import Blueprint, render_template, session, g, redirect, url_for
from flask_socketio import emit, disconnect
from langchain.callbacks.base import BaseCallbackHandler
from langchain.memory import ConversationBufferWindowMemory

from .auth import login_required_allow_demo
from .brain import reply
from .extensions import db, socketio
from .models import Ai, Knowledge
Expand All @@ -30,47 +29,40 @@ def on_llm_new_token(self, token: str, **kwargs) -> None:


@bp.route("/")
@login_required_allow_demo
def index():
"""Render the main ainteraction view."""
aifiles = db.session.query(Ai).all()
ailist = []
for aifile in aifiles:
ailist.append(
{
"id": aifile.id,
"name": aifile.name,
"input_keys": aifile.input_keys,
"input_labels": aifile.input_labels,
"greeting": aifile.greeting,
}
)
knowledge_entries = db.session.query(Knowledge).all()
knowledge_list = []
for knowledge_entry in knowledge_entries:
knowledge_list.append(
{
"id": knowledge_entry.id,
"name": knowledge_entry.name,
}
)
is_public = g.get("user") is None
ais = get_ai_data(only_public=is_public)

if is_public and not ais:
return redirect(url_for("auth.login"))

return render_template(
"ainteraction/index.html",
ais=json.dumps(ailist),
knowledges=json.dumps(knowledge_list),
ais=json.dumps(ais),
knowledges=json.dumps(get_knowledge_data(only_public=is_public)),
)


def handle_incoming_message(message):
"""Handle an incoming socket.io message from a user."""
if not session.get("user_id"):
is_public = session.get("user_id") is None
ai_id = message.get("aiId")
knowledge_id = message.get("knowledgeId")

if not ai_id:
disconnect()
return

if is_public and not is_ai_public(ai_id):
disconnect()
return

if is_public and knowledge_id and not is_knowledge_public(knowledge_id):
disconnect()
return

response_id = message.get("responseId")
ai_id = message.get("aiId")
knowledge_id = message.get("knowledgeId")
message_text = message.get("message", {}).get("text", "")

memory = ConversationBufferWindowMemory(k=3)
Expand Down Expand Up @@ -101,6 +93,49 @@ def init_app(_app):
socketio.on("message")(handle_incoming_message)


def get_ai_data(only_public=True):
"""Get data for all AIs."""
ai_query = db.session.query(Ai)
if only_public:
ai_query = ai_query.filter_by(is_public=True)
return [
{
"id": ai.id,
"name": ai.name,
"input_keys": ai.input_keys,
"input_labels": ai.input_labels,
"greeting": ai.greeting,
}
for ai in ai_query.all()
]


def get_knowledge_data(only_public=True):
"""Get data for all knowledges."""
knowledge_query = db.session.query(Knowledge)
if only_public:
knowledge_query = knowledge_query.filter_by(is_public=True)
return [
{
"id": knowledge.id,
"name": knowledge.name,
}
for knowledge in knowledge_query.all()
]


def is_ai_public(ai_id: int):
"""Check if an AI is public."""
ai = db.session.get(Ai, ai_id)
return bool(ai and ai.is_public)


def is_knowledge_public(knowledge_id: int):
"""Check if a knowledge is public."""
knowledge = db.session.get(Knowledge, knowledge_id)
return bool(knowledge and knowledge.is_public)


def send_next_token(response_id: int, token_text: str):
"""Send the next response token to the user."""
emit(
Expand Down
6 changes: 3 additions & 3 deletions backaind/api/ai.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""API to create, read, update and delete AIs."""
from flask import Blueprint, jsonify, request, make_response, abort

from ..auth import login_required, login_required_allow_demo
from ..auth import login_required
from ..brain import reset_global_chain
from ..extensions import db
from ..models import Ai
Expand Down Expand Up @@ -57,14 +57,14 @@ def validate(ai_json):


@bp.route("/", methods=["GET"])
@login_required_allow_demo
@login_required
def get_all_ais():
"""Get all AIs."""
return [ai.as_dict() for ai in db.session.query(Ai).all()]


@bp.route("/<int:ai_id>", methods=["GET"])
@login_required_allow_demo
@login_required
def get_ai(ai_id):
"""Get a specific AI."""
aifile = db.get_or_404(Ai, ai_id)
Expand Down
6 changes: 3 additions & 3 deletions backaind/api/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from langchain.document_loaders.base import BaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

from ..auth import login_required, login_required_allow_demo
from ..auth import login_required
from ..extensions import db
from ..knowledge import (
add_to_knowledge,
Expand Down Expand Up @@ -48,14 +48,14 @@ def validate(knowledge_json):


@bp.route("/", methods=["GET"])
@login_required_allow_demo
@login_required
def get_all_knowledge():
"""Get all knowledge."""
return [knowledge.as_dict() for knowledge in db.session.query(Knowledge).all()]


@bp.route("/<int:knowledge_id>", methods=["GET"])
@login_required_allow_demo
@login_required
def get_knowledge(knowledge_id):
"""Get a specific knowledge entry."""
knowledge = db.get_or_404(Knowledge, knowledge_id)
Expand Down
39 changes: 2 additions & 37 deletions backaind/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
from .extensions import db
from .models import User

DEMO_USER_NAME = "demo"
DEMO_USER_ID = -1

bp = Blueprint("auth", __name__, url_prefix="/auth")

Expand Down Expand Up @@ -55,17 +53,9 @@ def logout():
def load_logged_in_user():
"""Load the current user and add it to the global g instance."""
user_id = session.get("user_id")
g.is_demo_user = False

if user_id is None or user_id == DEMO_USER_ID:
if os.environ.get("ENABLE_DEMO_MODE") in ("1", "true", "True"):
g.user = User(username=DEMO_USER_NAME, id=DEMO_USER_ID)
g.is_demo_user = True
session.clear()
session["user_id"] = DEMO_USER_ID
else:
g.user = None
session.pop("user_id", None)
if user_id is None:
g.user = None
else:
g.user = db.session.get(User, user_id)

Expand All @@ -84,35 +74,10 @@ def set_password(username: str, password: str):
db.session.commit()


def is_demo_user():
"""Check if the current user is the demo user."""
return g.user is not None and g.user.id == DEMO_USER_ID


def login_required(view):
"""
Wrap a view to instead redirect to login page if the user is not logged in
or is the demo user.
For API requests, this does not redirect, but returns a 401 Unauthorized status code.
"""

@functools.wraps(view)
def wrapped_view(**kwargs):
if g.get("user") is None or is_demo_user():
if request.path.startswith("/api/"):
abort(401)
return redirect(url_for("auth.login"))

return view(**kwargs)

return wrapped_view


def login_required_allow_demo(view):
"""
Wrap a view to instead redirect to login page if the user is not logged in.
For API requests, this does not redirect, but returns a 401 Unauthorized status code.
This allows the demo user to access the view.
"""

@functools.wraps(view)
Expand Down
2 changes: 2 additions & 0 deletions backaind/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Ai(db.Model):
input_labels = db.Column(db.JSON, nullable=True)
chain = db.Column(db.JSON, nullable=False)
greeting = db.Column(db.String, nullable=True)
is_public = db.Column(db.Boolean, nullable=False, default=False)

def as_dict(self):
"""Return the model as a dictionary"""
Expand All @@ -44,6 +45,7 @@ class Knowledge(db.Model):
embeddings = db.Column(db.String, nullable=False)
chunk_size = db.Column(db.Integer, nullable=False)
persist_directory = db.Column(db.String, nullable=False)
is_public = db.Column(db.Boolean, nullable=False, default=False)

def as_dict(self):
"""Return the model as a dictionary"""
Expand Down
18 changes: 5 additions & 13 deletions backaind/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
from flask import Blueprint, request, flash, render_template, g

from .auth import (
login_required_allow_demo,
login_required,
is_password_correct,
set_password,
is_demo_user,
)
from .extensions import db
from .models import Setting
Expand Down Expand Up @@ -45,12 +44,10 @@


@bp.route("/password", methods=("GET", "POST"))
@login_required_allow_demo
@login_required
def password():
"""Render the password change page or change the user's password."""
if is_demo_user():
flash("You cannot change the password of the demo user.", "warning")
elif request.method == "POST":
if request.method == "POST":
current_password = request.form["current-password"]
new_password = request.form["new-password"]
new_password_confirmation = request.form["new-password-confirmation"]
Expand All @@ -69,15 +66,10 @@ def password():


@bp.route("/external-providers", methods=("GET", "POST"))
@login_required_allow_demo
@login_required
def external_providers():
"""Render the external providers page or save changed external providers settings."""
if is_demo_user():
flash(
"You cannot change the external providers settings of the demo user.",
"warning",
)
elif request.method == "POST":
if request.method == "POST":
for envvar in EXTERNAL_PROVIDER_ENVVARS:
setting = (
db.session.query(Setting)
Expand Down
2 changes: 1 addition & 1 deletion backaind/static/ownai-workshop.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 14 additions & 14 deletions backaind/static/ownai-workshop.js

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions backaind/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,18 @@
{% endblock %}
</head>
<body class="h-100 d-flex flex-column">
{% if g.is_demo_user %}
<script>
window.isDemoUser = true;
</script>
{% endif %}
<nav class="navbar border-bottom">
<div class="container-fluid">
{% include '_includes/main_menu.html' %}
{% if g.user %}
{% include '_includes/main_menu.html' %}
{% else %}
<a
href="/"
class="navbar-brand fw-bold d-flex align-items-center col-lg-4 link-body-emphasis text-decoration-none"
>
ownAI
</a>
{% endif %}
<div class="d-flex gap-3">
{% if g.user %}
{% include '_includes/user_menu.html' %}
Expand Down
4 changes: 2 additions & 2 deletions backaind/workshop.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Workshop is the place to invent, build, edit and work on AIs."""
from flask import Blueprint, render_template
from backaind.auth import login_required_allow_demo
from backaind.auth import login_required

bp = Blueprint("workshop", __name__, url_prefix="/workshop")

Expand All @@ -10,7 +10,7 @@
@bp.route("/ai/<_id>")
@bp.route("/knowledge/")
@bp.route("/knowledge/<_id>")
@login_required_allow_demo
@login_required
def index(_id=None):
"""Render the main workshop view."""
return render_template("workshop/index.html")
3 changes: 1 addition & 2 deletions frontaind/components/workshop/knowledge/FileDragDrop.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@

<script setup lang="ts">
import { ref, watch } from "vue";
import { throwOnFetchError, throwOnDemoUser } from "@/helpers/fetch";
import { throwOnFetchError } from "@/helpers/fetch";
const props = defineProps<{
knowledgeId: number;
Expand Down Expand Up @@ -137,7 +137,6 @@ const uploadFile = async (file: File, url: string): Promise<void> => {
formData.append("file", file);
try {
throwOnDemoUser();
const response = await fetch(url, {
method: "POST",
body: formData,
Expand Down
8 changes: 0 additions & 8 deletions frontaind/helpers/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,3 @@ export async function throwOnFetchError(response: Response) {
`HTTP error status: ${response.status}\nWe're sorry for that. Please see the server log for more information and contact us if you need help.`
);
}

export function throwOnDemoUser() {
if ("isDemoUser" in window && window["isDemoUser"]) {
throw new Error(
"Sorry, but this action is not allowed in demo mode. Please contact us if you need help."
);
}
}
Loading

0 comments on commit 9e3bfc3

Please sign in to comment.