Enhanced documentation

This commit is contained in:
Saifeddine ALOUI 2025-01-17 02:19:32 +01:00
parent 074fbb5988
commit b75d26861e
9 changed files with 644 additions and 246 deletions

2
app.py
View File

@ -17,7 +17,7 @@ from lollms.utilities import PackageManager
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
expected_ascii_colors_version = "0.5.0"
expected_ascii_colors_version = "0.5.1"
print(
f"Checking ascii_colors ({expected_ascii_colors_version}) ...", end="", flush=True
)

@ -1 +1 @@
Subproject commit 6d1c0b6b697f96b69e447ee5eccde642d4b278a2
Subproject commit 13b4e756578ac79b8c5926bd355dec6bfb434e80

370
tools/code_doc/code_doc.py Normal file
View File

@ -0,0 +1,370 @@
#!/usr/bin/env python3
"""
Enhanced Python Code Documentation Generator
This script creates a PyQt application that generates comprehensive markdown documentation
from Python source files. It extracts complete information about classes, functions,
methods, variables, decorators and their associated docstrings.
Author: Generated by LoLLMs
Date: 2025-01-16
"""
from pathlib import Path
import ast
import inspect
from typing import Dict, List, Optional, Tuple, Union, Any
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QTextEdit,
QFileDialog,
QMessageBox,
QLabel,
QStyle
)
from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtCore import Qt
import sys
class CodeParser:
"""
A class to parse Python source code and extract comprehensive documentation.
"""
def __init__(self):
"""Initialize the CodeParser."""
self.markdown: str = ""
self.current_class: Optional[str] = None
def get_decorator_list(self, node: ast.AST) -> str:
"""
Extract decorator information from a node.
Args:
node: AST node containing decorators
Returns:
str: Formatted decorator string
"""
decorators = []
for decorator in node.decorator_list:
if isinstance(decorator, ast.Name):
decorators.append(f"@{decorator.id}")
elif isinstance(decorator, ast.Call):
if isinstance(decorator.func, ast.Name):
args = []
for arg in decorator.args:
if isinstance(arg, ast.Constant):
args.append(str(arg.value))
if args:
decorators.append(f"@{decorator.func.id}({', '.join(args)})")
else:
decorators.append(f"@{decorator.func.id}()")
return "\n".join(decorators)
def get_arguments(self, node: ast.AST) -> str:
"""
Extract function arguments information.
Args:
node: AST node containing function arguments
Returns:
str: Formatted argument string
"""
args = []
# Process arguments
for arg in node.args.args:
arg_str = arg.arg
if hasattr(arg, 'annotation') and arg.annotation is not None:
if isinstance(arg.annotation, ast.Name):
arg_str += f": {arg.annotation.id}"
elif isinstance(arg.annotation, ast.Subscript):
arg_str += f": {ast.unparse(arg.annotation)}"
args.append(arg_str)
# Process default values
defaults = node.args.defaults
if defaults:
default_offset = len(args) - len(defaults)
for i, default in enumerate(defaults):
args[default_offset + i] += f" = {ast.unparse(default)}"
# Process variable arguments
if node.args.vararg:
args.append(f"*{node.args.vararg.arg}")
# Process keyword arguments
if node.args.kwarg:
args.append(f"**{node.args.kwarg.arg}")
return ", ".join(args)
def get_return_annotation(self, node: ast.AST) -> str:
"""
Extract return type annotation.
Args:
node: AST node containing return annotation
Returns:
str: Formatted return type string
"""
if node.returns:
return f" -> {ast.unparse(node.returns)}"
return ""
def parse_assign(self, node: ast.AST, level: int) -> None:
"""
Parse assignment nodes to extract class/module variables.
Args:
node: AST assignment node
level: Current nesting level
"""
for target in node.targets:
if isinstance(target, ast.Name):
name = target.id
value = ast.unparse(node.value)
if self.current_class:
self.markdown += f"{'#' * (level + 3)} Class Variable: {name} = {value}\n\n"
else:
self.markdown += f"{'#' * (level + 2)} Module Variable: {name} = {value}\n\n"
def parse_class_bases(self, node: ast.ClassDef) -> str:
"""
Extract class inheritance information.
Args:
node: Class definition node
Returns:
str: Formatted base classes string
"""
bases = []
for base in node.bases:
if isinstance(base, ast.Name):
bases.append(base.id)
elif isinstance(base, ast.Attribute):
bases.append(ast.unparse(base))
return ", ".join(bases)
def parse_node(self, node: ast.AST, level: int = 0) -> None:
"""
Parse an AST node and extract relevant documentation.
Args:
node: The AST node to parse
level: The current nesting level for markdown formatting
"""
# Handle class definitions
if isinstance(node, ast.ClassDef):
bases = self.parse_class_bases(node)
class_decor = self.get_decorator_list(node)
if class_decor:
self.markdown += f"```python\n{class_decor}\n```\n"
self.markdown += f"{'#' * (level + 2)} Class: {node.name}"
if bases:
self.markdown += f" ({bases})"
self.markdown += "\n\n"
if ast.get_docstring(node):
self.markdown += f"```python\n{ast.get_docstring(node)}\n```\n\n"
# Parse class body
self.current_class = node.name
for child in node.body:
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Assign)):
self.parse_node(child, level + 1)
self.current_class = None
# Handle function definitions
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
prefix = "Method" if self.current_class else "Function"
is_async = isinstance(node, ast.AsyncFunctionDef)
# Get decorators
decorators = self.get_decorator_list(node)
if decorators:
self.markdown += f"```python\n{decorators}\n```\n"
# Function signature
args = self.get_arguments(node)
returns = self.get_return_annotation(node)
async_prefix = "async " if is_async else ""
self.markdown += f"{'#' * (level + 2)} {prefix}: {async_prefix}{node.name}({args}){returns}\n\n"
# Function docstring
if ast.get_docstring(node):
self.markdown += f"```python\n{ast.get_docstring(node)}\n```\n\n"
# Handle assignments
elif isinstance(node, ast.Assign):
self.parse_assign(node, level)
def parse_imports(self, tree: ast.AST) -> None:
"""
Parse and format import statements.
Args:
tree: AST tree
"""
imports = []
for node in tree.body:
if isinstance(node, ast.Import):
for name in node.names:
imports.append(f"import {name.name}")
elif isinstance(node, ast.ImportFrom):
names = ", ".join(name.name for name in node.names)
imports.append(f"from {node.module} import {names}")
if imports:
self.markdown += "## Imports\n\n```python\n"
self.markdown += "\n".join(imports)
self.markdown += "\n```\n\n"
def parse_file(self, file_path: Path) -> str:
"""
Parse a Python file and generate comprehensive markdown documentation.
Args:
file_path: Path to the Python file
Returns:
str: Generated markdown documentation
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
source = f.read()
self.markdown = f"# Documentation for {file_path.name}\n\n"
tree = ast.parse(source)
# Get module docstring
module_doc = ast.get_docstring(tree)
if module_doc:
self.markdown += f"## Module Documentation\n\n```python\n{module_doc}\n```\n\n"
# Parse imports
self.parse_imports(tree)
# Parse all top-level nodes
for node in tree.body:
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef, ast.Assign)):
self.parse_node(node)
return self.markdown
except Exception as e:
return f"Error parsing file: {str(e)}"
class MainWindow(QMainWindow):
"""
Main application window.
"""
def __init__(self):
"""Initialize the main window."""
super().__init__()
self.setWindowTitle("Python Code Documentation Generator")
self.setMinimumSize(800, 600)
# Initialize UI
self.init_ui()
def init_ui(self) -> None:
"""Initialize the user interface."""
# Create central widget and layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Create top button bar
button_layout = QHBoxLayout()
# Open file button
self.open_btn = QPushButton("Open Python File")
self.open_btn.clicked.connect(self.open_file)
self.open_btn.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
button_layout.addWidget(self.open_btn)
# Copy button
self.copy_btn = QPushButton("Copy to Clipboard")
self.copy_btn.clicked.connect(self.copy_to_clipboard)
self.copy_btn.setIcon(self.style().standardIcon(QStyle.SP_DialogSaveButton))
button_layout.addWidget(self.copy_btn)
# Export button
self.export_btn = QPushButton("Export Markdown")
self.export_btn.clicked.connect(self.export_markdown)
self.export_btn.setIcon(self.style().standardIcon(QStyle.SP_DialogSaveButton))
button_layout.addWidget(self.export_btn)
layout.addLayout(button_layout)
# Create text display area
self.text_edit = QTextEdit()
self.text_edit.setFont(QFont("Consolas", 10))
self.text_edit.setReadOnly(True)
layout.addWidget(self.text_edit)
# Status bar
self.statusBar().showMessage("Ready")
def open_file(self) -> None:
"""Handle file opening."""
file_path, _ = QFileDialog.getOpenFileName(
self,
"Select Python File",
str(Path.home()),
"Python Files (*.py)"
)
if file_path:
parser = CodeParser()
markdown = parser.parse_file(Path(file_path))
self.text_edit.setPlainText(markdown)
self.statusBar().showMessage(f"Loaded: {file_path}")
def copy_to_clipboard(self) -> None:
"""Copy the generated markdown to clipboard."""
QApplication.clipboard().setText(self.text_edit.toPlainText())
self.statusBar().showMessage("Copied to clipboard!")
def export_markdown(self) -> None:
"""Export the markdown to a file."""
file_path, _ = QFileDialog.getSaveFileName(
self,
"Save Markdown File",
str(Path.home()),
"Markdown Files (*.md)"
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(self.text_edit.toPlainText())
self.statusBar().showMessage(f"Saved to: {file_path}")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to save file: {str(e)}")
def main():
"""Main application entry point."""
app = QApplication(sys.argv)
app.setStyle('Fusion')
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8
web/dist/assets/index-Dou72F5q.css vendored Normal file

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored
View File

@ -6,8 +6,8 @@
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LoLLMS WebUI</title>
<script type="module" crossorigin src="/assets/index-Pb4Zykpb.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-0YxKCr9N.css">
<script type="module" crossorigin src="/assets/index-DT-TjDjA.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dou72F5q.css">
</head>
<body>
<div id="app"></div>

View File

@ -1320,7 +1320,7 @@ export default {
"LoLLMs' version naming often contains clever easter eggs and references to AI advancements.",
"The 'Strawberry' version of LoLLMs was a playful nod to ChatGPT's internal codename for one of its versions.",
"The 'Saïph' version name was an intentional reference to Orion, anticipating OpenAI's rumored AGI-capable model codenamed 'Orion'.",
"LoLLMs' evolution can be traced through its version names: Warp, Starship, Robot, Brainwave, Strawberry, Feather and Saïph.",
"LoLLMs' evolution can be traced through its version names: Warp, Starship, Robot, Brainwave, Strawberry, Feather, Saïph, Nexus, Pulsar.",
"Each LoLLMs version name reflects either technological advancement or pays homage to significant developments in AI.",
"'Warp' and 'Starship' versions symbolized the quantum leap in AI capabilities and speed improvements.",
"'Robot' represented the system's growing autonomy and ability to perform complex tasks.",

View File

@ -1255,13 +1255,16 @@
v-model="configFile.rag_vectorizer_model"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-600 dark:border-gray-500"
>
<input v-if="configFile.rag_vectorizer === 'ollama'"
</div>
</div>
<div class="flex flex-col md:flex-row md:items-start gap-4">
<label class="text-sm font-bold w-64 pt-2">RAG server address:</label>
<input v-if="configFile.rag_vectorizer === 'ollama'"
v-model="configFile.rag_service_url"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-600 dark:border-gray-500"
>
</div>
</div>
<!-- Numeric inputs with sliders -->
<div class="space-y-6">
<!-- Chunk Size -->
@ -2191,54 +2194,61 @@
</Card>
<Card title="Whisper audio transcription" :is_subcard="true" class="pb-2 m-2">
<table class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<tr>
<td style="min-width: 200px;">
<label for="whisper_activate" class="text-sm font-bold" style="margin-right: 1rem;">Activate Whisper at startup:</label>
</td>
<td>
<div class="flex flex-row">
<input
type="checkbox"
id="whisper_activate"
required
v-model="configFile.whisper_activate"
@change="settingsChanged=true"
class="mt-1 px-2 py-1 border border-gray-300 rounded dark:bg-gray-600"
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 space-y-6">
<!-- Whisper Activation Section -->
<div class="flex items-center justify-between p-4 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-lg transition-all">
<label for="whisper_activate" class="text-gray-700 dark:text-gray-200 font-medium">
Activate Whisper at startup
</label>
<label class="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
id="whisper_activate"
v-model="configFile.whisper_activate"
@change="settingsChanged=true"
class="sr-only peer"
>
</div>
</td>
</tr>
<tr>
<td style="min-width: 200px;">
<label for="xtts_current_language" class="text-sm font-bold" style="margin-right: 1rem;"></label>
</td>
<td>
<button class="hover:text-primary bg-green-200 rounded-lg p-4 m-4 w-full text-center items-center" @click="reinstallWhisperService">install whisper</button>
</td>
</tr>
<tr>
<td style="min-width: 200px;">
<label for="whisper_model" class="text-sm font-bold" style="margin-right: 1rem;">Whisper model:</label>
</td>
<td>
<div class="flex flex-row">
<select
id="whisper_model"
v-model="configFile.whisper_model"
@change="settingsChanged=true"
class="w-full mt-1 px-2 py-1 border border-gray-300 rounded dark:bg-gray-600"
>
<!-- Options with language codes and corresponding language names -->
<option v-for="whispermodel in whisperModels" :key="whispermodel" :value="whispermodel">
{{ whispermodel }}
</option>
</select>
</div>
</td>
</tr>
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
</label>
</div>
</table>
<!-- Install Whisper Button Section -->
<div class="p-4">
<button
@click="reinstallWhisperService"
class="w-full py-3 px-4 bg-gradient-to-r from-green-400 to-green-500 hover:from-green-500 hover:to-green-600 text-white font-medium rounded-lg shadow-md hover:shadow-lg transition-all duration-300 flex items-center justify-center space-x-2"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
<span>Install Whisper</span>
</button>
</div>
<!-- Whisper Model Selection Section -->
<div class="p-4">
<label for="whisper_model" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
Whisper Model
</label>
<div class="relative">
<select
id="whisper_model"
v-model="configFile.whisper_model"
@change="settingsChanged=true"
class="block w-full px-4 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white appearance-none"
>
<option v-for="whispermodel in whisperModels" :key="whispermodel" :value="whispermodel">
{{ whispermodel }}
</option>
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 dark:text-gray-300">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
</div>
</div>
</div>
</Card>
<Card title="Open AI Whisper audio transcription" :is_subcard="true" class="pb-2 m-2">
<table class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
@ -3331,25 +3341,43 @@
</Card>
</Card>
<Card title="TTV settings" :is_subcard="true" class="pb-2 m-2">
<table class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<tr>
<td style="min-width: 200px;">
<label for="lumalabs_key" class="text-sm font-bold" style="margin-right: 1rem;">Lumalabs key:</label>
</td>
<td>
<div class="flex flex-row">
<input
type="text"
id="lumalabs_key"
required
v-model="configFile.lumalabs_key"
@change="settingsChanged=true"
class="mt-1 px-2 py-1 border border-gray-300 rounded dark:bg-gray-600"
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 max-w-2xl mx-auto">
<div class="flex flex-col md:flex-row items-start md:items-center space-y-4 md:space-y-0 md:space-x-6">
<div class="w-full md:w-1/3">
<label
for="lumalabs_key"
class="block text-sm font-semibold text-gray-700 dark:text-gray-200"
>
Lumalabs Key
</label>
</div>
<div class="w-full md:w-2/3">
<div class="relative">
<input
type="text"
id="lumalabs_key"
required
v-model="configFile.lumalabs_key"
@change="settingsChanged=true"
class="w-full px-4 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600
bg-gray-50 dark:bg-gray-700
text-gray-900 dark:text-white
focus:ring-2 focus:ring-blue-500 focus:border-transparent
transition duration-150 ease-in-out"
placeholder="Enter your Lumalabs API key"
>
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" />
</svg>
</div>
</div>
</td>
</tr>
</table>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Your API key will be securely stored
</p>
</div>
</div>
</div>
</Card>
<Card title="Misc" :is_shrunk="true" :is_subcard="true" class="pb-2 m-2">