mirror of
https://github.com/fsecada01/Pygentic-AI.git
synced 2025-06-15 03:26:03 +00:00
adding HTML components for HTMX integration
This commit is contained in:
parent
012ef4cc04
commit
0e65c811a1
2
.gitignore
vendored
2
.gitignore
vendored
@ -160,4 +160,4 @@ celerybeat-schedule*
|
||||
.ruff_cache
|
||||
|
||||
# JS dependencies
|
||||
node_modules
|
||||
node_modules
|
||||
|
@ -2,33 +2,24 @@ repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-ast
|
||||
- id: check-builtin-literals
|
||||
- id: check-case-conflict
|
||||
- id: check-docstring-first
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-json
|
||||
- id: check-merge-conflict
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-symlinks
|
||||
- id: check-toml
|
||||
- id: check-vcs-permalinks
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: check-builtin-literals
|
||||
- id: debug-statements
|
||||
exclude: tests/
|
||||
- id: destroyed-symlinks
|
||||
# - id: detect-aws-credentials
|
||||
- id: detect-private-key
|
||||
- id: end-of-file-fixer
|
||||
exclude: tests/test_changes/
|
||||
files: \.(py|sh|rst|yml|yaml)$
|
||||
- id: fix-byte-order-marker
|
||||
- id: pretty-format-json
|
||||
args: [--autofix]
|
||||
- id: sort-simple-yaml
|
||||
- id: mixed-line-ending
|
||||
- id: trailing-whitespace
|
||||
- hooks:
|
||||
- id: add-trailing-comma
|
||||
repo: 'https://github.com/asottile/add-trailing-comma'
|
||||
rev: v3.1.0
|
||||
- hooks:
|
||||
- id: pyupgrade
|
||||
repo: 'https://github.com/asottile/pyupgrade'
|
||||
rev: v3.19.1
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
@ -52,4 +43,4 @@ repos:
|
||||
rev: v3.19.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py311-plus]
|
||||
args: [--py313-plus]
|
||||
|
@ -34,4 +34,4 @@ RUN mkdir -p /tmp/log/celery && \
|
||||
RUN find . -name "*.sh" -exec chmod +x {} \;
|
||||
RUN echo $WORKDIR
|
||||
RUN /bin/bash -c 'source $WORKDIR/docker/pygentic_ai/python_build.sh'
|
||||
CMD /bin/bash -c 'source $WORKDIR/docker/pygentic_ai/python_start.sh'
|
||||
CMD /bin/bash -c 'source $WORKDIR/docker/pygentic_ai/python_start.sh'
|
||||
|
@ -7,4 +7,4 @@ isort
|
||||
jupyterlab
|
||||
jupyterlab-code-formatter
|
||||
pre-commit
|
||||
ruff
|
||||
ruff
|
||||
|
@ -20,7 +20,8 @@ app = create_app(debug=debug_arg, settings_obj=app_settings)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(
|
||||
request: Request, exc: RequestValidationError
|
||||
request: Request,
|
||||
exc: RequestValidationError,
|
||||
):
|
||||
"""
|
||||
Custom validation error messaging for end-users. This reduces ambiguity
|
||||
@ -35,7 +36,8 @@ async def validation_exception_handler(
|
||||
content = {"status_code": 10422, "message": exc_str, "data": None}
|
||||
|
||||
return JSONResponse(
|
||||
content=content, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
content=content,
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
)
|
||||
|
||||
|
||||
@ -63,7 +65,7 @@ async def unicorn_exception_handler(request: Request, exc: UnicornException):
|
||||
status_code=418,
|
||||
content={
|
||||
"message": f"Oops! {exc.name} did something. "
|
||||
"There goes a rainbow..."
|
||||
"There goes a rainbow...",
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -10,7 +10,8 @@ from backend.logger import logger
|
||||
|
||||
@swot_agent.tool(prepare=report_tool_usage)
|
||||
async def fetch_website_content(
|
||||
_ctx: RunContext[SwotAgentDeps], url: str
|
||||
_ctx: RunContext[SwotAgentDeps],
|
||||
url: str,
|
||||
) -> str:
|
||||
"""
|
||||
Fetches the HTML content of the given URL via httpx and beautifulsoup
|
||||
@ -70,7 +71,9 @@ async def analyze_competition(
|
||||
|
||||
@swot_agent.tool(prepare=report_tool_usage)
|
||||
async def get_reddit_insights(
|
||||
ctx: RunContext[SwotAgentDeps], query: str, subreddit_name: str = "python"
|
||||
ctx: RunContext[SwotAgentDeps],
|
||||
query: str,
|
||||
subreddit_name: str = "python",
|
||||
):
|
||||
"""
|
||||
A tool to gain insights from a subreddit. Data is returned as string
|
||||
@ -89,14 +92,15 @@ async def get_reddit_insights(
|
||||
insights.append(
|
||||
f"Title: {post.title}\n"
|
||||
f"URL: {post.url}\n"
|
||||
f"Content: {post.selftext}\n"
|
||||
f"Content: {post.selftext}\n",
|
||||
)
|
||||
|
||||
return "\n".join(insights)
|
||||
|
||||
|
||||
async def run_agent(
|
||||
url: str, deps: SwotAgentDeps = SwotAgentDeps()
|
||||
url: str,
|
||||
deps: SwotAgentDeps = SwotAgentDeps(),
|
||||
) -> SwotAnalysis | Exception:
|
||||
"""
|
||||
Runs the SWOT Analysis Agent
|
||||
|
@ -5,7 +5,8 @@ from backend.core.core import SwotAgentDeps
|
||||
|
||||
|
||||
async def report_tool_usage(
|
||||
ctx: RunContext[SwotAgentDeps], tool_def: ToolDefinition
|
||||
ctx: RunContext[SwotAgentDeps],
|
||||
tool_def: ToolDefinition,
|
||||
) -> ToolDefinition:
|
||||
"""
|
||||
Reports tool usage + results to an update function
|
||||
@ -18,7 +19,8 @@ async def report_tool_usage(
|
||||
|
||||
if ctx.deps.update_status_func:
|
||||
await ctx.deps.update_status_func(
|
||||
ctx.deps.request, f"Using tool: {tool_def.name}..."
|
||||
ctx.deps.request,
|
||||
f"Using tool: {tool_def.name}...",
|
||||
)
|
||||
ctx.deps.tool_history.append(tool_def.name)
|
||||
|
||||
|
@ -38,7 +38,9 @@ def as_form(cls: type[BaseModel]):
|
||||
inspect.Parameter.POSITIONAL_ONLY,
|
||||
default=model_field.default,
|
||||
annotation=Annotated[
|
||||
model_field.annotation, *model_field.metadata, Form()
|
||||
model_field.annotation,
|
||||
*model_field.metadata,
|
||||
Form(),
|
||||
],
|
||||
)
|
||||
for field_name, model_field in cls.model_fields.items()
|
||||
@ -56,7 +58,7 @@ def as_form(cls: type[BaseModel]):
|
||||
return cls
|
||||
|
||||
|
||||
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
async def get_async_session() -> AsyncGenerator[AsyncSession]:
|
||||
"""
|
||||
Get async SQLA session via generator function
|
||||
"""
|
||||
@ -88,7 +90,7 @@ async def run_out_of_band(
|
||||
"""
|
||||
async with async_sessionaker() as oob_session:
|
||||
await oob_session.connection(
|
||||
execution_options={"isolation_level": "AUTOCOMMIT"}
|
||||
execution_options={"isolation_level": "AUTOCOMMIT"},
|
||||
)
|
||||
|
||||
result = await oob_session.execute(statement)
|
||||
@ -96,7 +98,10 @@ async def run_out_of_band(
|
||||
if merge_results:
|
||||
return (
|
||||
await session_inst.run_sync(
|
||||
merge_frozen_result, statement, result.freeze(), load=False
|
||||
merge_frozen_result,
|
||||
statement,
|
||||
result.freeze(),
|
||||
load=False,
|
||||
)
|
||||
)()
|
||||
else:
|
||||
@ -114,7 +119,7 @@ async def check_db_exists(engine_inst):
|
||||
from sqlalchemy import inspect # noqa
|
||||
|
||||
tables = await conn.run_sync(
|
||||
lambda sync_conn: inspect(sync_conn).get_table_names()
|
||||
lambda sync_conn: inspect(sync_conn).get_table_names(),
|
||||
)
|
||||
logger.info(tables)
|
||||
if not tables:
|
||||
@ -146,7 +151,9 @@ async def create_db(
|
||||
|
||||
|
||||
def create_db_engine(
|
||||
db_url: str, async_bool: bool = False, echo_bool: bool = False
|
||||
db_url: str,
|
||||
async_bool: bool = False,
|
||||
echo_bool: bool = False,
|
||||
):
|
||||
"""
|
||||
|
||||
@ -194,11 +201,15 @@ sync_engine = create_db_engine(db_url, echo_bool=echo)
|
||||
session_inst = Session(sync_engine, expire_on_commit=False)
|
||||
|
||||
async_session_maker = sessionmaker(
|
||||
bind=engine, class_=AsyncSession, expire_on_commit=False
|
||||
bind=engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False,
|
||||
)
|
||||
|
||||
session_maker = sessionmaker(
|
||||
bind=engine, class_=Session, expire_on_commit=False
|
||||
bind=engine,
|
||||
class_=Session,
|
||||
expire_on_commit=False,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -26,7 +26,7 @@ list(
|
||||
enqueue=True,
|
||||
),
|
||||
["info", "debug", "error", "success", "warning", "critical"],
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@ -53,7 +53,8 @@ class InterceptHandler(logging.Handler):
|
||||
|
||||
log = logger.bind(request_id="app")
|
||||
log.opt(depth=depth, exception=record.exc_info).log(
|
||||
level, record.getMessage()
|
||||
level,
|
||||
record.getMessage(),
|
||||
)
|
||||
|
||||
|
||||
|
@ -7,7 +7,8 @@ from backend.settings.prod import Settings as ProdSettings
|
||||
from backend.utils import get_val
|
||||
|
||||
server_types = enum.StrEnum(
|
||||
"ServerTypes", {x.upper(): x for x in ("dev", "uat", "staging", "prod")}
|
||||
"ServerTypes",
|
||||
{x.upper(): x for x in ("dev", "uat", "staging", "prod")},
|
||||
)
|
||||
|
||||
|
||||
|
@ -56,7 +56,7 @@ def get_db_val(
|
||||
[
|
||||
hasattr(db_configs, x)
|
||||
for x in ("name", "user", "password", "host", "port", "kwargs")
|
||||
]
|
||||
],
|
||||
):
|
||||
url = f"{dialect}:{schema_sep}{db_configs.host}"
|
||||
else:
|
||||
|
@ -47,7 +47,7 @@ all_dialects = enum.Enum(
|
||||
y
|
||||
for x in (pg_dialects, mysql_dialects, sqlite_dialects)
|
||||
for y in x
|
||||
]
|
||||
],
|
||||
]
|
||||
},
|
||||
)
|
||||
|
@ -26,10 +26,10 @@ catalog = Catalog(jinja_env=templates.env)
|
||||
list(
|
||||
map(
|
||||
lambda folder: catalog.add_folder(
|
||||
os.path.join(frontend, "components", folder) # noqa
|
||||
os.path.join(frontend, "components", folder), # noqa
|
||||
),
|
||||
("main", "forms", "snippets"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
user_frontend.mount(
|
||||
@ -87,7 +87,7 @@ async def get_status(request: Request):
|
||||
result = ANALYSIS_COMPLETE_MESSAGE in messages
|
||||
logger.info(
|
||||
f"Status check - Session ID: {'session_id'}, Messages: "
|
||||
f"{messages}"
|
||||
f"{messages}",
|
||||
)
|
||||
|
||||
context.update({"messages": messages, "result": result})
|
||||
|
@ -46,11 +46,14 @@ async def update_status(session_id: str, message: Any) -> None:
|
||||
else:
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(
|
||||
None, emulate_tool_completion, session_id, message
|
||||
None,
|
||||
emulate_tool_completion,
|
||||
session_id,
|
||||
message,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Status messages for session {session_id}: {status_store[session_id]}"
|
||||
f"Status messages for session {session_id}: {status_store[session_id]}",
|
||||
)
|
||||
|
||||
|
||||
@ -67,7 +70,8 @@ async def run_agent_with_progress(session_id, url):
|
||||
deps = SwotAgentDeps(
|
||||
request=None,
|
||||
update_status_func=lambda request, msg: update_status(
|
||||
session_id, msg
|
||||
session_id,
|
||||
msg,
|
||||
),
|
||||
)
|
||||
|
||||
@ -78,7 +82,7 @@ async def run_agent_with_progress(session_id, url):
|
||||
result_store[session_id] = result
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"An unexpected error occurred. See here: " f"{type(e), e, e.args}"
|
||||
f"An unexpected error occurred. See here: " f"{type(e), e, e.args}",
|
||||
)
|
||||
await update_status(session_id, f"Unexpected error: {e}")
|
||||
raise
|
||||
|
@ -62,7 +62,7 @@ def get_val(val: str, default: str | int | bool | None = None, **kwargs):
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Env Var {val} is not populated in the environment "
|
||||
f"or within the configuration files"
|
||||
f"or within the configuration files",
|
||||
)
|
||||
|
||||
return val
|
||||
|
1
src/frontend/static/css/bulma-collapsible.min.css
vendored
Normal file
1
src/frontend/static/css/bulma-collapsible.min.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.is-collapsible{overflow-y:hidden;transition:height .2s ease}.is-collapsible.is-active{transition:height .2s ease}.is-collapsible.message-body{padding:0!important}.is-collapsible.message-body .message-body-content{padding:1.25em 1.5em}
|
21559
src/frontend/static/css/bulma.css
vendored
Normal file
21559
src/frontend/static/css/bulma.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
src/frontend/static/css/bulma.css.map
Normal file
1
src/frontend/static/css/bulma.css.map
Normal file
File diff suppressed because one or more lines are too long
3
src/frontend/static/css/bulma.min.css
vendored
Normal file
3
src/frontend/static/css/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
src/frontend/static/css/bulma.scss
vendored
Normal file
4
src/frontend/static/css/bulma.scss
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
@charset "utf-8";
|
||||
|
||||
/*! bulma.io v1.0.3 | MIT License | github.com/jgthms/bulma */
|
||||
@use "sass";
|
1
src/frontend/static/js/bulma-collapsible.min.js
vendored
Normal file
1
src/frontend/static/js/bulma-collapsible.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/frontend/static/js/bulma.js
Normal file
1
src/frontend/static/js/bulma.js
Normal file
File diff suppressed because one or more lines are too long
56
src/frontend/static/js/htmx.js
Normal file
56
src/frontend/static/js/htmx.js
Normal file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env node
|
||||
// build a --template, or a whole project --root directory
|
||||
// default context filename is `index.js` for dirs, unless --context
|
||||
// specify a --build directory to output there, instead of STDOUT
|
||||
|
||||
var
|
||||
preprocess= require('./preprocess'),
|
||||
// here you can pre-process the response before final output
|
||||
|
||||
DELIM= ["::", "::"], // or use ["<\\?js", "\\?>"] to get `1<?js2?>3` → `123`
|
||||
// describe your document context in JSON, then use <?js ?> for the template
|
||||
// i.e. root/index.json {a:1, b:"hi"} , index.html <!doctype html> ::b+a:: → "hi1"
|
||||
|
||||
JSON= /^\s*\{[^]*\}\s*$/gi
|
||||
// relaxed Javascript Object, for use with eval([js]).pop()
|
||||
|
||||
|
||||
function HTMX (delim){
|
||||
function unpickle (__ctx, __js){ with (__ctx){ return eval (__js) }}
|
||||
|
||||
var
|
||||
EMPTY_STR= '',
|
||||
NOT_DELIM= ANY= "([^]*?)"
|
||||
|
||||
return (function (delim, pre){
|
||||
return function renderTemplateWithContext (t, c){
|
||||
var
|
||||
r= t.split( new RegExp(delim.join( NOT_DELIM), 'ig')),
|
||||
n= 0, s,
|
||||
c= c || {}
|
||||
|
||||
do {
|
||||
s= 3 *n + 1
|
||||
if (r[s] && r[s].match( JSON)){
|
||||
|
||||
r[s]= unpickle( c, '[' + r[s] + ']')[0]
|
||||
r[s]= pre.call( c, r[s])
|
||||
r[s]= r[s].return
|
||||
} else {
|
||||
r[s]= unpickle( c, r[s])
|
||||
}
|
||||
n += 1
|
||||
} while (void 0 !== r[3 *n])
|
||||
return r.join(EMPTY_STR)
|
||||
}
|
||||
})(delim || DELIM, preprocess)
|
||||
}
|
||||
|
||||
|
||||
if (require.main === module){
|
||||
if (2 >= process.argv.length)
|
||||
throw new Error("use --root with --build, or --template with --context")
|
||||
require('./cli')(HTMX)
|
||||
|
||||
} else
|
||||
module.exports= HTMX
|
10716
src/frontend/static/js/jquery.js
vendored
Normal file
10716
src/frontend/static/js/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
src/frontend/static/js/jquery.min.js
vendored
Normal file
2
src/frontend/static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/frontend/static/js/jquery.min.map
Normal file
1
src/frontend/static/js/jquery.min.map
Normal file
File diff suppressed because one or more lines are too long
0
src/frontend/templates/components/main/Footer.jinja
Normal file
0
src/frontend/templates/components/main/Footer.jinja
Normal file
9
src/frontend/templates/components/main/Header.jinja
Normal file
9
src/frontend/templates/components/main/Header.jinja
Normal file
@ -0,0 +1,9 @@
|
||||
{# def
|
||||
title: str
|
||||
#}
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }}</title>
|
||||
{{ slot }}
|
||||
</head>
|
0
src/frontend/templates/components/main/Nav.jinja
Normal file
0
src/frontend/templates/components/main/Nav.jinja
Normal file
17
src/frontend/templates/components/snippets/Base.jinja
Normal file
17
src/frontend/templates/components/snippets/Base.jinja
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<Header title="Pygentic AI">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE-edge, chrome=1">
|
||||
<Css url="{{ url_for('static', path='/css/bulma.min.css') }}"></Css>
|
||||
<Css url="{{ url_for('static', path='/css/bulma-calendar.min.css') }}"></Css>
|
||||
<Css url="{{ url_for('static', path='/css/bulmatags-input.min-css') }}"></Css>
|
||||
<Css url="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
</Css>
|
||||
</Header>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
9
src/frontend/templates/components/snippets/Css.jinja
Normal file
9
src/frontend/templates/components/snippets/Css.jinja
Normal file
@ -0,0 +1,9 @@
|
||||
{# def
|
||||
url: str,
|
||||
rel: str = 'stylesheet'
|
||||
#}
|
||||
|
||||
<link rel={{ rel }}
|
||||
href={{ url }}
|
||||
{{ slot }}
|
||||
/>
|
8
src/frontend/templates/components/snippets/Js.jinja
Normal file
8
src/frontend/templates/components/snippets/Js.jinja
Normal file
@ -0,0 +1,8 @@
|
||||
{# def
|
||||
url: str
|
||||
#}
|
||||
|
||||
<script
|
||||
src={{ url }}
|
||||
{{ slot }}
|
||||
></script>
|
Loading…
x
Reference in New Issue
Block a user