Spaces:
Build error
Build error
| import time | |
| import uuid | |
| from typing import Optional | |
| from sqlalchemy.orm import Session | |
| from open_webui.internal.db import Base, JSONField, get_db, get_db_context | |
| from open_webui.models.groups import Groups | |
| from open_webui.models.users import Users, UserResponse | |
| from open_webui.models.prompt_history import PromptHistories | |
| from open_webui.models.access_grants import AccessGrantModel, AccessGrants | |
| from pydantic import BaseModel, ConfigDict, Field | |
| from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON, or_, func, cast | |
| #################### | |
| # Prompts DB Schema | |
| #################### | |
| class Prompt(Base): | |
| __tablename__ = "prompt" | |
| id = Column(Text, primary_key=True) | |
| command = Column(String, unique=True, index=True) | |
| user_id = Column(String) | |
| name = Column(Text) | |
| content = Column(Text) | |
| data = Column(JSON, nullable=True) | |
| meta = Column(JSON, nullable=True) | |
| tags = Column(JSON, nullable=True) | |
| is_active = Column(Boolean, default=True) | |
| version_id = Column(Text, nullable=True) # Points to active history entry | |
| created_at = Column(BigInteger, nullable=True) | |
| updated_at = Column(BigInteger, nullable=True) | |
| class PromptModel(BaseModel): | |
| id: Optional[str] = None | |
| command: str | |
| user_id: str | |
| name: str | |
| content: str | |
| data: Optional[dict] = None | |
| meta: Optional[dict] = None | |
| tags: Optional[list[str]] = None | |
| is_active: Optional[bool] = True | |
| version_id: Optional[str] = None | |
| created_at: Optional[int] = None | |
| updated_at: Optional[int] = None | |
| access_grants: list[AccessGrantModel] = Field(default_factory=list) | |
| model_config = ConfigDict(from_attributes=True) | |
| #################### | |
| # Forms | |
| #################### | |
| class PromptUserResponse(PromptModel): | |
| user: Optional[UserResponse] = None | |
| class PromptAccessResponse(PromptUserResponse): | |
| write_access: Optional[bool] = False | |
| class PromptListResponse(BaseModel): | |
| items: list[PromptUserResponse] | |
| total: int | |
| class PromptAccessListResponse(BaseModel): | |
| items: list[PromptAccessResponse] | |
| total: int | |
| class PromptForm(BaseModel): | |
| command: str | |
| name: str # Changed from title | |
| content: str | |
| data: Optional[dict] = None | |
| meta: Optional[dict] = None | |
| tags: Optional[list[str]] = None | |
| access_grants: Optional[list[dict]] = None | |
| version_id: Optional[str] = None # Active version | |
| commit_message: Optional[str] = None # For history tracking | |
| is_production: Optional[bool] = True # Whether to set new version as production | |
| class PromptsTable: | |
| def _get_access_grants( | |
| self, prompt_id: str, db: Optional[Session] = None | |
| ) -> list[AccessGrantModel]: | |
| return AccessGrants.get_grants_by_resource("prompt", prompt_id, db=db) | |
| def _to_prompt_model( | |
| self, | |
| prompt: Prompt, | |
| access_grants: Optional[list[AccessGrantModel]] = None, | |
| db: Optional[Session] = None, | |
| ) -> PromptModel: | |
| prompt_data = PromptModel.model_validate(prompt).model_dump( | |
| exclude={"access_grants"} | |
| ) | |
| prompt_data["access_grants"] = ( | |
| access_grants | |
| if access_grants is not None | |
| else self._get_access_grants(prompt_data["id"], db=db) | |
| ) | |
| return PromptModel.model_validate(prompt_data) | |
| def insert_new_prompt( | |
| self, user_id: str, form_data: PromptForm, db: Optional[Session] = None | |
| ) -> Optional[PromptModel]: | |
| now = int(time.time()) | |
| prompt_id = str(uuid.uuid4()) | |
| prompt = PromptModel( | |
| id=prompt_id, | |
| user_id=user_id, | |
| command=form_data.command, | |
| name=form_data.name, | |
| content=form_data.content, | |
| data=form_data.data or {}, | |
| meta=form_data.meta or {}, | |
| tags=form_data.tags or [], | |
| access_grants=[], | |
| is_active=True, | |
| created_at=now, | |
| updated_at=now, | |
| ) | |
| try: | |
| with get_db_context(db) as db: | |
| result = Prompt(**prompt.model_dump(exclude={"access_grants"})) | |
| db.add(result) | |
| db.commit() | |
| db.refresh(result) | |
| AccessGrants.set_access_grants( | |
| "prompt", prompt_id, form_data.access_grants, db=db | |
| ) | |
| if result: | |
| current_access_grants = self._get_access_grants(prompt_id, db=db) | |
| snapshot = { | |
| "name": form_data.name, | |
| "content": form_data.content, | |
| "command": form_data.command, | |
| "data": form_data.data or {}, | |
| "meta": form_data.meta or {}, | |
| "tags": form_data.tags or [], | |
| "access_grants": [ | |
| grant.model_dump() for grant in current_access_grants | |
| ], | |
| } | |
| history_entry = PromptHistories.create_history_entry( | |
| prompt_id=prompt_id, | |
| snapshot=snapshot, | |
| user_id=user_id, | |
| parent_id=None, # Initial commit has no parent | |
| commit_message=form_data.commit_message or "Initial version", | |
| db=db, | |
| ) | |
| # Set the initial version as the production version | |
| if history_entry: | |
| result.version_id = history_entry.id | |
| db.commit() | |
| db.refresh(result) | |
| return self._to_prompt_model(result, db=db) | |
| else: | |
| return None | |
| except Exception: | |
| return None | |
| def get_prompt_by_id( | |
| self, prompt_id: str, db: Optional[Session] = None | |
| ) -> Optional[PromptModel]: | |
| """Get prompt by UUID.""" | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(id=prompt_id).first() | |
| if prompt: | |
| return self._to_prompt_model(prompt, db=db) | |
| return None | |
| except Exception: | |
| return None | |
| def get_prompt_by_command( | |
| self, command: str, db: Optional[Session] = None | |
| ) -> Optional[PromptModel]: | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(command=command).first() | |
| if prompt: | |
| return self._to_prompt_model(prompt, db=db) | |
| return None | |
| except Exception: | |
| return None | |
| def get_prompts(self, db: Optional[Session] = None) -> list[PromptUserResponse]: | |
| with get_db_context(db) as db: | |
| all_prompts = ( | |
| db.query(Prompt) | |
| .filter(Prompt.is_active == True) | |
| .order_by(Prompt.updated_at.desc()) | |
| .all() | |
| ) | |
| user_ids = list(set(prompt.user_id for prompt in all_prompts)) | |
| prompt_ids = [prompt.id for prompt in all_prompts] | |
| users = Users.get_users_by_user_ids(user_ids, db=db) if user_ids else [] | |
| users_dict = {user.id: user for user in users} | |
| grants_map = AccessGrants.get_grants_by_resources( | |
| "prompt", prompt_ids, db=db | |
| ) | |
| prompts = [] | |
| for prompt in all_prompts: | |
| user = users_dict.get(prompt.user_id) | |
| prompts.append( | |
| PromptUserResponse.model_validate( | |
| { | |
| **self._to_prompt_model( | |
| prompt, | |
| access_grants=grants_map.get(prompt.id, []), | |
| db=db, | |
| ).model_dump(), | |
| "user": user.model_dump() if user else None, | |
| } | |
| ) | |
| ) | |
| return prompts | |
| def get_prompts_by_user_id( | |
| self, user_id: str, permission: str = "write", db: Optional[Session] = None | |
| ) -> list[PromptUserResponse]: | |
| prompts = self.get_prompts(db=db) | |
| user_group_ids = { | |
| group.id for group in Groups.get_groups_by_member_id(user_id, db=db) | |
| } | |
| return [ | |
| prompt | |
| for prompt in prompts | |
| if prompt.user_id == user_id | |
| or AccessGrants.has_access( | |
| user_id=user_id, | |
| resource_type="prompt", | |
| resource_id=prompt.id, | |
| permission=permission, | |
| user_group_ids=user_group_ids, | |
| db=db, | |
| ) | |
| ] | |
| def search_prompts( | |
| self, | |
| user_id: str, | |
| filter: dict = {}, | |
| skip: int = 0, | |
| limit: int = 30, | |
| db: Optional[Session] = None, | |
| ) -> PromptListResponse: | |
| with get_db_context(db) as db: | |
| from open_webui.models.users import User, UserModel | |
| # Join with User table for user filtering and sorting | |
| query = db.query(Prompt, User).outerjoin(User, User.id == Prompt.user_id) | |
| if filter: | |
| query_key = filter.get("query") | |
| if query_key: | |
| query = query.filter( | |
| or_( | |
| Prompt.name.ilike(f"%{query_key}%"), | |
| Prompt.command.ilike(f"%{query_key}%"), | |
| Prompt.content.ilike(f"%{query_key}%"), | |
| User.name.ilike(f"%{query_key}%"), | |
| User.email.ilike(f"%{query_key}%"), | |
| ) | |
| ) | |
| view_option = filter.get("view_option") | |
| if view_option == "created": | |
| query = query.filter(Prompt.user_id == user_id) | |
| elif view_option == "shared": | |
| query = query.filter(Prompt.user_id != user_id) | |
| # Apply access grant filtering | |
| query = AccessGrants.has_permission_filter( | |
| db=db, | |
| query=query, | |
| DocumentModel=Prompt, | |
| filter=filter, | |
| resource_type="prompt", | |
| permission="read", | |
| ) | |
| tag = filter.get("tag") | |
| if tag: | |
| # Search for tag in JSON array field | |
| like_pattern = f'%"{tag.lower()}"%' | |
| tags_text = func.lower(cast(Prompt.tags, String)) | |
| query = query.filter(tags_text.like(like_pattern)) | |
| order_by = filter.get("order_by") | |
| direction = filter.get("direction") | |
| if order_by == "name": | |
| if direction == "asc": | |
| query = query.order_by(Prompt.name.asc()) | |
| else: | |
| query = query.order_by(Prompt.name.desc()) | |
| elif order_by == "created_at": | |
| if direction == "asc": | |
| query = query.order_by(Prompt.created_at.asc()) | |
| else: | |
| query = query.order_by(Prompt.created_at.desc()) | |
| elif order_by == "updated_at": | |
| if direction == "asc": | |
| query = query.order_by(Prompt.updated_at.asc()) | |
| else: | |
| query = query.order_by(Prompt.updated_at.desc()) | |
| else: | |
| query = query.order_by(Prompt.updated_at.desc()) | |
| else: | |
| query = query.order_by(Prompt.updated_at.desc()) | |
| # Count BEFORE pagination | |
| total = query.count() | |
| if skip: | |
| query = query.offset(skip) | |
| if limit: | |
| query = query.limit(limit) | |
| items = query.all() | |
| prompt_ids = [prompt.id for prompt, _ in items] | |
| grants_map = AccessGrants.get_grants_by_resources( | |
| "prompt", prompt_ids, db=db | |
| ) | |
| prompts = [] | |
| for prompt, user in items: | |
| prompts.append( | |
| PromptUserResponse( | |
| **self._to_prompt_model( | |
| prompt, | |
| access_grants=grants_map.get(prompt.id, []), | |
| db=db, | |
| ).model_dump(), | |
| user=( | |
| UserResponse(**UserModel.model_validate(user).model_dump()) | |
| if user | |
| else None | |
| ), | |
| ) | |
| ) | |
| return PromptListResponse(items=prompts, total=total) | |
| def update_prompt_by_command( | |
| self, | |
| command: str, | |
| form_data: PromptForm, | |
| user_id: str, | |
| db: Optional[Session] = None, | |
| ) -> Optional[PromptModel]: | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(command=command).first() | |
| if not prompt: | |
| return None | |
| latest_history = PromptHistories.get_latest_history_entry( | |
| prompt.id, db=db | |
| ) | |
| parent_id = latest_history.id if latest_history else None | |
| current_access_grants = self._get_access_grants(prompt.id, db=db) | |
| # Check if content changed to decide on history creation | |
| content_changed = ( | |
| prompt.name != form_data.name | |
| or prompt.content != form_data.content | |
| or form_data.access_grants is not None | |
| ) | |
| # Update prompt fields | |
| prompt.name = form_data.name | |
| prompt.content = form_data.content | |
| prompt.data = form_data.data or prompt.data | |
| prompt.meta = form_data.meta or prompt.meta | |
| prompt.updated_at = int(time.time()) | |
| if form_data.access_grants is not None: | |
| AccessGrants.set_access_grants( | |
| "prompt", prompt.id, form_data.access_grants, db=db | |
| ) | |
| current_access_grants = self._get_access_grants(prompt.id, db=db) | |
| db.commit() | |
| # Create history entry only if content changed | |
| if content_changed: | |
| snapshot = { | |
| "name": form_data.name, | |
| "content": form_data.content, | |
| "command": command, | |
| "data": form_data.data or {}, | |
| "meta": form_data.meta or {}, | |
| "access_grants": [ | |
| grant.model_dump() for grant in current_access_grants | |
| ], | |
| } | |
| history_entry = PromptHistories.create_history_entry( | |
| prompt_id=prompt.id, | |
| snapshot=snapshot, | |
| user_id=user_id, | |
| parent_id=parent_id, | |
| commit_message=form_data.commit_message, | |
| db=db, | |
| ) | |
| # Set as production if flag is True (default) | |
| if form_data.is_production and history_entry: | |
| prompt.version_id = history_entry.id | |
| db.commit() | |
| return self._to_prompt_model(prompt, db=db) | |
| except Exception: | |
| return None | |
| def update_prompt_by_id( | |
| self, | |
| prompt_id: str, | |
| form_data: PromptForm, | |
| user_id: str, | |
| db: Optional[Session] = None, | |
| ) -> Optional[PromptModel]: | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(id=prompt_id).first() | |
| if not prompt: | |
| return None | |
| latest_history = PromptHistories.get_latest_history_entry( | |
| prompt.id, db=db | |
| ) | |
| parent_id = latest_history.id if latest_history else None | |
| current_access_grants = self._get_access_grants(prompt.id, db=db) | |
| # Check if content changed to decide on history creation | |
| content_changed = ( | |
| prompt.name != form_data.name | |
| or prompt.command != form_data.command | |
| or prompt.content != form_data.content | |
| or form_data.access_grants is not None | |
| or (form_data.tags is not None and prompt.tags != form_data.tags) | |
| ) | |
| # Update prompt fields | |
| prompt.name = form_data.name | |
| prompt.command = form_data.command | |
| prompt.content = form_data.content | |
| prompt.data = form_data.data or prompt.data | |
| prompt.meta = form_data.meta or prompt.meta | |
| if form_data.tags is not None: | |
| prompt.tags = form_data.tags | |
| if form_data.access_grants is not None: | |
| AccessGrants.set_access_grants( | |
| "prompt", prompt.id, form_data.access_grants, db=db | |
| ) | |
| current_access_grants = self._get_access_grants(prompt.id, db=db) | |
| prompt.updated_at = int(time.time()) | |
| db.commit() | |
| # Create history entry only if content changed | |
| if content_changed: | |
| snapshot = { | |
| "name": form_data.name, | |
| "content": form_data.content, | |
| "command": prompt.command, | |
| "data": form_data.data or {}, | |
| "meta": form_data.meta or {}, | |
| "tags": prompt.tags or [], | |
| "access_grants": [ | |
| grant.model_dump() for grant in current_access_grants | |
| ], | |
| } | |
| history_entry = PromptHistories.create_history_entry( | |
| prompt_id=prompt.id, | |
| snapshot=snapshot, | |
| user_id=user_id, | |
| parent_id=parent_id, | |
| commit_message=form_data.commit_message, | |
| db=db, | |
| ) | |
| # Set as production if flag is True (default) | |
| if form_data.is_production and history_entry: | |
| prompt.version_id = history_entry.id | |
| db.commit() | |
| return self._to_prompt_model(prompt, db=db) | |
| except Exception: | |
| return None | |
| def update_prompt_metadata( | |
| self, | |
| prompt_id: str, | |
| name: str, | |
| command: str, | |
| tags: Optional[list[str]] = None, | |
| db: Optional[Session] = None, | |
| ) -> Optional[PromptModel]: | |
| """Update only name, command, and tags (no history created).""" | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(id=prompt_id).first() | |
| if not prompt: | |
| return None | |
| prompt.name = name | |
| prompt.command = command | |
| if tags is not None: | |
| prompt.tags = tags | |
| prompt.updated_at = int(time.time()) | |
| db.commit() | |
| return self._to_prompt_model(prompt, db=db) | |
| except Exception: | |
| return None | |
| def update_prompt_version( | |
| self, | |
| prompt_id: str, | |
| version_id: str, | |
| db: Optional[Session] = None, | |
| ) -> Optional[PromptModel]: | |
| """Set the active version of a prompt and restore content from that version's snapshot.""" | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(id=prompt_id).first() | |
| if not prompt: | |
| return None | |
| history_entry = PromptHistories.get_history_entry_by_id( | |
| version_id, db=db | |
| ) | |
| if not history_entry: | |
| return None | |
| # Restore prompt content from the snapshot | |
| snapshot = history_entry.snapshot | |
| if snapshot: | |
| prompt.name = snapshot.get("name", prompt.name) | |
| prompt.content = snapshot.get("content", prompt.content) | |
| prompt.data = snapshot.get("data", prompt.data) | |
| prompt.meta = snapshot.get("meta", prompt.meta) | |
| prompt.tags = snapshot.get("tags", prompt.tags) | |
| # Note: command and access_grants are not restored from snapshot | |
| prompt.version_id = version_id | |
| prompt.updated_at = int(time.time()) | |
| db.commit() | |
| return self._to_prompt_model(prompt, db=db) | |
| except Exception: | |
| return None | |
| def toggle_prompt_active( | |
| self, prompt_id: str, db: Optional[Session] = None | |
| ) -> Optional[PromptModel]: | |
| """Toggle the is_active flag on a prompt.""" | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(id=prompt_id).first() | |
| if prompt: | |
| prompt.is_active = not prompt.is_active | |
| prompt.updated_at = int(time.time()) | |
| db.commit() | |
| db.refresh(prompt) | |
| return self._to_prompt_model(prompt, db=db) | |
| return None | |
| except Exception: | |
| return None | |
| def delete_prompt_by_command( | |
| self, command: str, db: Optional[Session] = None | |
| ) -> bool: | |
| """Permanently delete a prompt and its history.""" | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(command=command).first() | |
| if prompt: | |
| PromptHistories.delete_history_by_prompt_id(prompt.id, db=db) | |
| AccessGrants.revoke_all_access("prompt", prompt.id, db=db) | |
| db.delete(prompt) | |
| db.commit() | |
| return True | |
| return False | |
| except Exception: | |
| return False | |
| def delete_prompt_by_id(self, prompt_id: str, db: Optional[Session] = None) -> bool: | |
| """Permanently delete a prompt and its history.""" | |
| try: | |
| with get_db_context(db) as db: | |
| prompt = db.query(Prompt).filter_by(id=prompt_id).first() | |
| if prompt: | |
| PromptHistories.delete_history_by_prompt_id(prompt.id, db=db) | |
| AccessGrants.revoke_all_access("prompt", prompt.id, db=db) | |
| db.delete(prompt) | |
| db.commit() | |
| return True | |
| return False | |
| except Exception: | |
| return False | |
| def get_tags(self, db: Optional[Session] = None) -> list[str]: | |
| try: | |
| with get_db_context(db) as db: | |
| prompts = db.query(Prompt).filter_by(is_active=True).all() | |
| tags = set() | |
| for prompt in prompts: | |
| if prompt.tags: | |
| for tag in prompt.tags: | |
| if tag: | |
| tags.add(tag) | |
| return sorted(list(tags)) | |
| except Exception: | |
| return [] | |
| Prompts = PromptsTable() | |