vanna 0.7.9__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vanna/__init__.py +167 -395
- vanna/agents/__init__.py +7 -0
- vanna/capabilities/__init__.py +17 -0
- vanna/capabilities/agent_memory/__init__.py +21 -0
- vanna/capabilities/agent_memory/base.py +103 -0
- vanna/capabilities/agent_memory/models.py +53 -0
- vanna/capabilities/file_system/__init__.py +14 -0
- vanna/capabilities/file_system/base.py +71 -0
- vanna/capabilities/file_system/models.py +25 -0
- vanna/capabilities/sql_runner/__init__.py +13 -0
- vanna/capabilities/sql_runner/base.py +37 -0
- vanna/capabilities/sql_runner/models.py +13 -0
- vanna/components/__init__.py +92 -0
- vanna/components/base.py +11 -0
- vanna/components/rich/__init__.py +83 -0
- vanna/components/rich/containers/__init__.py +7 -0
- vanna/components/rich/containers/card.py +20 -0
- vanna/components/rich/data/__init__.py +9 -0
- vanna/components/rich/data/chart.py +17 -0
- vanna/components/rich/data/dataframe.py +93 -0
- vanna/components/rich/feedback/__init__.py +21 -0
- vanna/components/rich/feedback/badge.py +16 -0
- vanna/components/rich/feedback/icon_text.py +14 -0
- vanna/components/rich/feedback/log_viewer.py +41 -0
- vanna/components/rich/feedback/notification.py +19 -0
- vanna/components/rich/feedback/progress.py +37 -0
- vanna/components/rich/feedback/status_card.py +28 -0
- vanna/components/rich/feedback/status_indicator.py +14 -0
- vanna/components/rich/interactive/__init__.py +21 -0
- vanna/components/rich/interactive/button.py +95 -0
- vanna/components/rich/interactive/task_list.py +58 -0
- vanna/components/rich/interactive/ui_state.py +93 -0
- vanna/components/rich/specialized/__init__.py +7 -0
- vanna/components/rich/specialized/artifact.py +20 -0
- vanna/components/rich/text.py +16 -0
- vanna/components/simple/__init__.py +15 -0
- vanna/components/simple/image.py +15 -0
- vanna/components/simple/link.py +15 -0
- vanna/components/simple/text.py +11 -0
- vanna/core/__init__.py +193 -0
- vanna/core/_compat.py +19 -0
- vanna/core/agent/__init__.py +10 -0
- vanna/core/agent/agent.py +1407 -0
- vanna/core/agent/config.py +123 -0
- vanna/core/audit/__init__.py +28 -0
- vanna/core/audit/base.py +299 -0
- vanna/core/audit/models.py +131 -0
- vanna/core/component_manager.py +329 -0
- vanna/core/components.py +53 -0
- vanna/core/enhancer/__init__.py +11 -0
- vanna/core/enhancer/base.py +94 -0
- vanna/core/enhancer/default.py +118 -0
- vanna/core/enricher/__init__.py +10 -0
- vanna/core/enricher/base.py +59 -0
- vanna/core/errors.py +47 -0
- vanna/core/evaluation/__init__.py +81 -0
- vanna/core/evaluation/base.py +186 -0
- vanna/core/evaluation/dataset.py +254 -0
- vanna/core/evaluation/evaluators.py +376 -0
- vanna/core/evaluation/report.py +289 -0
- vanna/core/evaluation/runner.py +313 -0
- vanna/core/filter/__init__.py +10 -0
- vanna/core/filter/base.py +67 -0
- vanna/core/lifecycle/__init__.py +10 -0
- vanna/core/lifecycle/base.py +83 -0
- vanna/core/llm/__init__.py +16 -0
- vanna/core/llm/base.py +40 -0
- vanna/core/llm/models.py +61 -0
- vanna/core/middleware/__init__.py +10 -0
- vanna/core/middleware/base.py +69 -0
- vanna/core/observability/__init__.py +11 -0
- vanna/core/observability/base.py +88 -0
- vanna/core/observability/models.py +47 -0
- vanna/core/recovery/__init__.py +11 -0
- vanna/core/recovery/base.py +84 -0
- vanna/core/recovery/models.py +32 -0
- vanna/core/registry.py +278 -0
- vanna/core/rich_component.py +156 -0
- vanna/core/simple_component.py +27 -0
- vanna/core/storage/__init__.py +14 -0
- vanna/core/storage/base.py +46 -0
- vanna/core/storage/models.py +46 -0
- vanna/core/system_prompt/__init__.py +13 -0
- vanna/core/system_prompt/base.py +36 -0
- vanna/core/system_prompt/default.py +157 -0
- vanna/core/tool/__init__.py +18 -0
- vanna/core/tool/base.py +70 -0
- vanna/core/tool/models.py +84 -0
- vanna/core/user/__init__.py +17 -0
- vanna/core/user/base.py +29 -0
- vanna/core/user/models.py +25 -0
- vanna/core/user/request_context.py +70 -0
- vanna/core/user/resolver.py +42 -0
- vanna/core/validation.py +164 -0
- vanna/core/workflow/__init__.py +12 -0
- vanna/core/workflow/base.py +254 -0
- vanna/core/workflow/default.py +789 -0
- vanna/examples/__init__.py +1 -0
- vanna/examples/__main__.py +44 -0
- vanna/examples/anthropic_quickstart.py +80 -0
- vanna/examples/artifact_example.py +293 -0
- vanna/examples/claude_sqlite_example.py +236 -0
- vanna/examples/coding_agent_example.py +300 -0
- vanna/examples/custom_system_prompt_example.py +174 -0
- vanna/examples/default_workflow_handler_example.py +208 -0
- vanna/examples/email_auth_example.py +340 -0
- vanna/examples/evaluation_example.py +269 -0
- vanna/examples/extensibility_example.py +262 -0
- vanna/examples/minimal_example.py +67 -0
- vanna/examples/mock_auth_example.py +227 -0
- vanna/examples/mock_custom_tool.py +311 -0
- vanna/examples/mock_quickstart.py +79 -0
- vanna/examples/mock_quota_example.py +145 -0
- vanna/examples/mock_rich_components_demo.py +396 -0
- vanna/examples/mock_sqlite_example.py +223 -0
- vanna/examples/openai_quickstart.py +83 -0
- vanna/examples/primitive_components_demo.py +305 -0
- vanna/examples/quota_lifecycle_example.py +139 -0
- vanna/examples/visualization_example.py +251 -0
- vanna/integrations/__init__.py +17 -0
- vanna/integrations/anthropic/__init__.py +9 -0
- vanna/integrations/anthropic/llm.py +270 -0
- vanna/integrations/azureopenai/__init__.py +9 -0
- vanna/integrations/azureopenai/llm.py +329 -0
- vanna/integrations/azuresearch/__init__.py +7 -0
- vanna/integrations/azuresearch/agent_memory.py +413 -0
- vanna/integrations/bigquery/__init__.py +5 -0
- vanna/integrations/bigquery/sql_runner.py +81 -0
- vanna/integrations/chromadb/__init__.py +104 -0
- vanna/integrations/chromadb/agent_memory.py +416 -0
- vanna/integrations/clickhouse/__init__.py +5 -0
- vanna/integrations/clickhouse/sql_runner.py +82 -0
- vanna/integrations/duckdb/__init__.py +5 -0
- vanna/integrations/duckdb/sql_runner.py +65 -0
- vanna/integrations/faiss/__init__.py +7 -0
- vanna/integrations/faiss/agent_memory.py +431 -0
- vanna/integrations/google/__init__.py +9 -0
- vanna/integrations/google/gemini.py +370 -0
- vanna/integrations/hive/__init__.py +5 -0
- vanna/integrations/hive/sql_runner.py +87 -0
- vanna/integrations/local/__init__.py +17 -0
- vanna/integrations/local/agent_memory/__init__.py +7 -0
- vanna/integrations/local/agent_memory/in_memory.py +285 -0
- vanna/integrations/local/audit.py +59 -0
- vanna/integrations/local/file_system.py +242 -0
- vanna/integrations/local/file_system_conversation_store.py +255 -0
- vanna/integrations/local/storage.py +62 -0
- vanna/integrations/marqo/__init__.py +7 -0
- vanna/integrations/marqo/agent_memory.py +354 -0
- vanna/integrations/milvus/__init__.py +7 -0
- vanna/integrations/milvus/agent_memory.py +458 -0
- vanna/integrations/mock/__init__.py +9 -0
- vanna/integrations/mock/llm.py +65 -0
- vanna/integrations/mssql/__init__.py +5 -0
- vanna/integrations/mssql/sql_runner.py +66 -0
- vanna/integrations/mysql/__init__.py +5 -0
- vanna/integrations/mysql/sql_runner.py +92 -0
- vanna/integrations/ollama/__init__.py +7 -0
- vanna/integrations/ollama/llm.py +252 -0
- vanna/integrations/openai/__init__.py +10 -0
- vanna/integrations/openai/llm.py +267 -0
- vanna/integrations/openai/responses.py +163 -0
- vanna/integrations/opensearch/__init__.py +7 -0
- vanna/integrations/opensearch/agent_memory.py +411 -0
- vanna/integrations/oracle/__init__.py +5 -0
- vanna/integrations/oracle/sql_runner.py +75 -0
- vanna/integrations/pinecone/__init__.py +7 -0
- vanna/integrations/pinecone/agent_memory.py +329 -0
- vanna/integrations/plotly/__init__.py +5 -0
- vanna/integrations/plotly/chart_generator.py +313 -0
- vanna/integrations/postgres/__init__.py +9 -0
- vanna/integrations/postgres/sql_runner.py +112 -0
- vanna/integrations/premium/agent_memory/__init__.py +7 -0
- vanna/integrations/premium/agent_memory/premium.py +186 -0
- vanna/integrations/presto/__init__.py +5 -0
- vanna/integrations/presto/sql_runner.py +107 -0
- vanna/integrations/qdrant/__init__.py +7 -0
- vanna/integrations/qdrant/agent_memory.py +461 -0
- vanna/integrations/snowflake/__init__.py +5 -0
- vanna/integrations/snowflake/sql_runner.py +147 -0
- vanna/integrations/sqlite/__init__.py +9 -0
- vanna/integrations/sqlite/sql_runner.py +65 -0
- vanna/integrations/weaviate/__init__.py +7 -0
- vanna/integrations/weaviate/agent_memory.py +428 -0
- vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_embeddings.py +11 -11
- vanna/legacy/__init__.py +403 -0
- vanna/legacy/adapter.py +463 -0
- vanna/{advanced → legacy/advanced}/__init__.py +3 -1
- vanna/{anthropic → legacy/anthropic}/anthropic_chat.py +9 -7
- vanna/{azuresearch → legacy/azuresearch}/azuresearch_vector.py +79 -41
- vanna/{base → legacy/base}/base.py +224 -217
- vanna/legacy/bedrock/__init__.py +1 -0
- vanna/{bedrock → legacy/bedrock}/bedrock_converse.py +13 -12
- vanna/{chromadb → legacy/chromadb}/chromadb_vector.py +3 -1
- vanna/legacy/cohere/__init__.py +2 -0
- vanna/{cohere → legacy/cohere}/cohere_chat.py +19 -14
- vanna/{cohere → legacy/cohere}/cohere_embeddings.py +25 -19
- vanna/{deepseek → legacy/deepseek}/deepseek_chat.py +5 -6
- vanna/legacy/faiss/__init__.py +1 -0
- vanna/{faiss → legacy/faiss}/faiss.py +113 -59
- vanna/{flask → legacy/flask}/__init__.py +84 -43
- vanna/{flask → legacy/flask}/assets.py +5 -5
- vanna/{flask → legacy/flask}/auth.py +5 -4
- vanna/{google → legacy/google}/bigquery_vector.py +75 -42
- vanna/{google → legacy/google}/gemini_chat.py +7 -3
- vanna/{hf → legacy/hf}/hf.py +0 -1
- vanna/{milvus → legacy/milvus}/milvus_vector.py +58 -35
- vanna/{mock → legacy/mock}/llm.py +0 -1
- vanna/legacy/mock/vectordb.py +67 -0
- vanna/legacy/ollama/ollama.py +110 -0
- vanna/{openai → legacy/openai}/openai_chat.py +2 -6
- vanna/legacy/opensearch/opensearch_vector.py +369 -0
- vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
- vanna/legacy/oracle/oracle_vector.py +584 -0
- vanna/{pgvector → legacy/pgvector}/pgvector.py +42 -13
- vanna/{qdrant → legacy/qdrant}/qdrant.py +2 -6
- vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
- vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
- vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
- vanna/{remote.py → legacy/remote.py} +28 -26
- vanna/{utils.py → legacy/utils.py} +6 -11
- vanna/{vannadb → legacy/vannadb}/vannadb_vector.py +115 -46
- vanna/{vllm → legacy/vllm}/vllm.py +5 -6
- vanna/{weaviate → legacy/weaviate}/weaviate_vector.py +59 -40
- vanna/{xinference → legacy/xinference}/xinference.py +6 -6
- vanna/py.typed +0 -0
- vanna/servers/__init__.py +16 -0
- vanna/servers/__main__.py +8 -0
- vanna/servers/base/__init__.py +18 -0
- vanna/servers/base/chat_handler.py +65 -0
- vanna/servers/base/models.py +111 -0
- vanna/servers/base/rich_chat_handler.py +141 -0
- vanna/servers/base/templates.py +331 -0
- vanna/servers/cli/__init__.py +7 -0
- vanna/servers/cli/server_runner.py +204 -0
- vanna/servers/fastapi/__init__.py +7 -0
- vanna/servers/fastapi/app.py +163 -0
- vanna/servers/fastapi/routes.py +183 -0
- vanna/servers/flask/__init__.py +7 -0
- vanna/servers/flask/app.py +132 -0
- vanna/servers/flask/routes.py +137 -0
- vanna/tools/__init__.py +41 -0
- vanna/tools/agent_memory.py +322 -0
- vanna/tools/file_system.py +879 -0
- vanna/tools/python.py +222 -0
- vanna/tools/run_sql.py +165 -0
- vanna/tools/visualize_data.py +195 -0
- vanna/utils/__init__.py +0 -0
- vanna/web_components/__init__.py +44 -0
- vanna-2.0.0.dist-info/METADATA +485 -0
- vanna-2.0.0.dist-info/RECORD +289 -0
- vanna-2.0.0.dist-info/entry_points.txt +3 -0
- vanna/bedrock/__init__.py +0 -1
- vanna/cohere/__init__.py +0 -2
- vanna/faiss/__init__.py +0 -1
- vanna/mock/vectordb.py +0 -55
- vanna/ollama/ollama.py +0 -103
- vanna/opensearch/opensearch_vector.py +0 -392
- vanna/opensearch/opensearch_vector_semantic.py +0 -175
- vanna/oracle/oracle_vector.py +0 -585
- vanna/qianfan/Qianfan_Chat.py +0 -165
- vanna/qianfan/Qianfan_embeddings.py +0 -36
- vanna/qianwen/QianwenAI_chat.py +0 -133
- vanna-0.7.9.dist-info/METADATA +0 -408
- vanna-0.7.9.dist-info/RECORD +0 -79
- /vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_Chat.py +0 -0
- /vanna/{ZhipuAI → legacy/ZhipuAI}/__init__.py +0 -0
- /vanna/{anthropic → legacy/anthropic}/__init__.py +0 -0
- /vanna/{azuresearch → legacy/azuresearch}/__init__.py +0 -0
- /vanna/{base → legacy/base}/__init__.py +0 -0
- /vanna/{chromadb → legacy/chromadb}/__init__.py +0 -0
- /vanna/{deepseek → legacy/deepseek}/__init__.py +0 -0
- /vanna/{exceptions → legacy/exceptions}/__init__.py +0 -0
- /vanna/{google → legacy/google}/__init__.py +0 -0
- /vanna/{hf → legacy/hf}/__init__.py +0 -0
- /vanna/{local.py → legacy/local.py} +0 -0
- /vanna/{marqo → legacy/marqo}/__init__.py +0 -0
- /vanna/{marqo → legacy/marqo}/marqo.py +0 -0
- /vanna/{milvus → legacy/milvus}/__init__.py +0 -0
- /vanna/{mistral → legacy/mistral}/__init__.py +0 -0
- /vanna/{mistral → legacy/mistral}/mistral.py +0 -0
- /vanna/{mock → legacy/mock}/__init__.py +0 -0
- /vanna/{mock → legacy/mock}/embedding.py +0 -0
- /vanna/{ollama → legacy/ollama}/__init__.py +0 -0
- /vanna/{openai → legacy/openai}/__init__.py +0 -0
- /vanna/{openai → legacy/openai}/openai_embeddings.py +0 -0
- /vanna/{opensearch → legacy/opensearch}/__init__.py +0 -0
- /vanna/{oracle → legacy/oracle}/__init__.py +0 -0
- /vanna/{pgvector → legacy/pgvector}/__init__.py +0 -0
- /vanna/{pinecone → legacy/pinecone}/__init__.py +0 -0
- /vanna/{pinecone → legacy/pinecone}/pinecone_vector.py +0 -0
- /vanna/{qdrant → legacy/qdrant}/__init__.py +0 -0
- /vanna/{qianfan → legacy/qianfan}/__init__.py +0 -0
- /vanna/{qianwen → legacy/qianwen}/QianwenAI_embeddings.py +0 -0
- /vanna/{qianwen → legacy/qianwen}/__init__.py +0 -0
- /vanna/{types → legacy/types}/__init__.py +0 -0
- /vanna/{vannadb → legacy/vannadb}/__init__.py +0 -0
- /vanna/{vllm → legacy/vllm}/__init__.py +0 -0
- /vanna/{weaviate → legacy/weaviate}/__init__.py +0 -0
- /vanna/{xinference → legacy/xinference}/__init__.py +0 -0
- {vanna-0.7.9.dist-info → vanna-2.0.0.dist-info}/WHEEL +0 -0
- {vanna-0.7.9.dist-info → vanna-2.0.0.dist-info}/licenses/LICENSE +0 -0
vanna/tools/python.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Python-specific tooling built on top of the file system service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shlex
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any, List, Optional, Sequence, Type
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from vanna.components import (
|
|
12
|
+
UiComponent,
|
|
13
|
+
CardComponent,
|
|
14
|
+
ComponentType,
|
|
15
|
+
NotificationComponent,
|
|
16
|
+
SimpleTextComponent,
|
|
17
|
+
)
|
|
18
|
+
from vanna.core.tool import Tool, ToolContext, ToolResult
|
|
19
|
+
|
|
20
|
+
from .file_system import CommandResult, FileSystem, LocalFileSystem
|
|
21
|
+
|
|
22
|
+
MAX_OUTPUT_LENGTH = 4000
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RunPythonFileArgs(BaseModel):
|
|
26
|
+
"""Arguments required to execute a Python file."""
|
|
27
|
+
|
|
28
|
+
filename: str = Field(
|
|
29
|
+
description="Python file to execute (relative to the workspace root)"
|
|
30
|
+
)
|
|
31
|
+
arguments: Sequence[str] = Field(
|
|
32
|
+
default_factory=list,
|
|
33
|
+
description="Optional arguments to pass to the Python script",
|
|
34
|
+
)
|
|
35
|
+
timeout_seconds: Optional[float] = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
ge=0,
|
|
38
|
+
description="Optional timeout for the command in seconds",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RunPythonFileTool(Tool[RunPythonFileArgs]):
|
|
43
|
+
"""Execute a Python file using the provided file system service."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, file_system: Optional[FileSystem] = None):
|
|
46
|
+
self.file_system = file_system or LocalFileSystem()
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def name(self) -> str:
|
|
50
|
+
return "run_python_file"
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def description(self) -> str:
|
|
54
|
+
return "Execute a Python file using the workspace interpreter"
|
|
55
|
+
|
|
56
|
+
def get_args_schema(self) -> Type[RunPythonFileArgs]:
|
|
57
|
+
return RunPythonFileArgs
|
|
58
|
+
|
|
59
|
+
async def execute(
|
|
60
|
+
self, context: ToolContext, args: RunPythonFileArgs
|
|
61
|
+
) -> ToolResult:
|
|
62
|
+
exists = await self.file_system.exists(args.filename, context)
|
|
63
|
+
if not exists:
|
|
64
|
+
message = f"Cannot execute '{args.filename}' because it does not exist."
|
|
65
|
+
return _error_result(message)
|
|
66
|
+
|
|
67
|
+
command_parts = [sys.executable, args.filename]
|
|
68
|
+
command_parts.extend(args.arguments)
|
|
69
|
+
command = _quote_command(command_parts)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
result = await self.file_system.run_bash(
|
|
73
|
+
command,
|
|
74
|
+
context,
|
|
75
|
+
timeout=args.timeout_seconds,
|
|
76
|
+
)
|
|
77
|
+
except TimeoutError as exc:
|
|
78
|
+
message = str(exc)
|
|
79
|
+
return _error_result(message)
|
|
80
|
+
|
|
81
|
+
summary = f"Executed python {args.filename} (exit code {result.returncode})."
|
|
82
|
+
success = result.returncode == 0
|
|
83
|
+
return _result_from_command(summary, command, result, success=success)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PipInstallArgs(BaseModel):
|
|
87
|
+
"""Arguments required to run pip install."""
|
|
88
|
+
|
|
89
|
+
packages: List[str] = Field(
|
|
90
|
+
description="Packages (with optional specifiers) to install", min_length=1
|
|
91
|
+
)
|
|
92
|
+
upgrade: bool = Field(
|
|
93
|
+
default=False,
|
|
94
|
+
description="Whether to include --upgrade in the pip invocation",
|
|
95
|
+
)
|
|
96
|
+
extra_args: Sequence[str] = Field(
|
|
97
|
+
default_factory=list,
|
|
98
|
+
description="Additional arguments to pass to pip install",
|
|
99
|
+
)
|
|
100
|
+
timeout_seconds: Optional[float] = Field(
|
|
101
|
+
default=None,
|
|
102
|
+
ge=0,
|
|
103
|
+
description="Optional timeout for the command in seconds",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class PipInstallTool(Tool[PipInstallArgs]):
|
|
108
|
+
"""Install Python packages using pip inside the workspace environment."""
|
|
109
|
+
|
|
110
|
+
def __init__(self, file_system: Optional[FileSystem] = None):
|
|
111
|
+
self.file_system = file_system or LocalFileSystem()
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def name(self) -> str:
|
|
115
|
+
return "pip_install"
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def description(self) -> str:
|
|
119
|
+
return "Install Python packages using pip"
|
|
120
|
+
|
|
121
|
+
def get_args_schema(self) -> Type[PipInstallArgs]:
|
|
122
|
+
return PipInstallArgs
|
|
123
|
+
|
|
124
|
+
async def execute(self, context: ToolContext, args: PipInstallArgs) -> ToolResult:
|
|
125
|
+
command_parts = [sys.executable, "-m", "pip", "install"]
|
|
126
|
+
if args.upgrade:
|
|
127
|
+
command_parts.append("--upgrade")
|
|
128
|
+
command_parts.extend(args.packages)
|
|
129
|
+
command_parts.extend(args.extra_args)
|
|
130
|
+
command = _quote_command(command_parts)
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
result = await self.file_system.run_bash(
|
|
134
|
+
command,
|
|
135
|
+
context,
|
|
136
|
+
timeout=args.timeout_seconds,
|
|
137
|
+
)
|
|
138
|
+
except TimeoutError as exc:
|
|
139
|
+
return _error_result(str(exc))
|
|
140
|
+
|
|
141
|
+
success = result.returncode == 0
|
|
142
|
+
summary = (
|
|
143
|
+
"pip install completed successfully"
|
|
144
|
+
if success
|
|
145
|
+
else f"pip install failed (exit code {result.returncode})."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return _result_from_command(summary, command, result, success=success)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def create_python_tools(file_system: Optional[FileSystem] = None) -> List[Tool[Any]]:
|
|
152
|
+
"""Create Python-specific tools backed by a shared file system service."""
|
|
153
|
+
|
|
154
|
+
fs = file_system or LocalFileSystem()
|
|
155
|
+
return [
|
|
156
|
+
RunPythonFileTool(fs),
|
|
157
|
+
PipInstallTool(fs),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _quote_command(parts: Sequence[str]) -> str:
|
|
162
|
+
return " ".join(shlex.quote(part) for part in parts)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _truncate(text: str, limit: int = MAX_OUTPUT_LENGTH) -> str:
|
|
166
|
+
if len(text) <= limit:
|
|
167
|
+
return text
|
|
168
|
+
return f"{text[: limit - 1]}…"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _result_from_command(
|
|
172
|
+
summary: str,
|
|
173
|
+
command: str,
|
|
174
|
+
result: CommandResult,
|
|
175
|
+
*,
|
|
176
|
+
success: bool = True,
|
|
177
|
+
) -> ToolResult:
|
|
178
|
+
stdout = result.stdout.strip()
|
|
179
|
+
stderr = result.stderr.strip()
|
|
180
|
+
|
|
181
|
+
blocks: List[str] = [f"$ {command}"]
|
|
182
|
+
if stdout:
|
|
183
|
+
blocks.append("STDOUT:\n" + _truncate(stdout))
|
|
184
|
+
if stderr:
|
|
185
|
+
blocks.append("STDERR:\n" + _truncate(stderr))
|
|
186
|
+
if not stdout and not stderr:
|
|
187
|
+
blocks.append("(no output)")
|
|
188
|
+
|
|
189
|
+
content = "\n\n".join(blocks)
|
|
190
|
+
card_status = "success" if success else "error"
|
|
191
|
+
component = CardComponent(
|
|
192
|
+
type=ComponentType.CARD,
|
|
193
|
+
title="Command Result",
|
|
194
|
+
content=content,
|
|
195
|
+
status=card_status,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return ToolResult(
|
|
199
|
+
success=success,
|
|
200
|
+
result_for_llm=f"{summary}\n\n{content}",
|
|
201
|
+
ui_component=UiComponent(
|
|
202
|
+
rich_component=component,
|
|
203
|
+
simple_component=SimpleTextComponent(text=summary),
|
|
204
|
+
),
|
|
205
|
+
error=None if success else content,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _error_result(message: str) -> ToolResult:
|
|
210
|
+
return ToolResult(
|
|
211
|
+
success=False,
|
|
212
|
+
result_for_llm=message,
|
|
213
|
+
ui_component=UiComponent(
|
|
214
|
+
rich_component=NotificationComponent(
|
|
215
|
+
type=ComponentType.NOTIFICATION,
|
|
216
|
+
level="error",
|
|
217
|
+
message=message,
|
|
218
|
+
),
|
|
219
|
+
simple_component=SimpleTextComponent(text=message),
|
|
220
|
+
),
|
|
221
|
+
error=message,
|
|
222
|
+
)
|
vanna/tools/run_sql.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Generic SQL query execution tool with dependency injection."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional, Type, cast
|
|
4
|
+
import uuid
|
|
5
|
+
from vanna.core.tool import Tool, ToolContext, ToolResult
|
|
6
|
+
from vanna.components import (
|
|
7
|
+
UiComponent,
|
|
8
|
+
DataFrameComponent,
|
|
9
|
+
NotificationComponent,
|
|
10
|
+
ComponentType,
|
|
11
|
+
SimpleTextComponent,
|
|
12
|
+
)
|
|
13
|
+
from vanna.capabilities.sql_runner import SqlRunner, RunSqlToolArgs
|
|
14
|
+
from vanna.capabilities.file_system import FileSystem
|
|
15
|
+
from vanna.integrations.local import LocalFileSystem
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RunSqlTool(Tool[RunSqlToolArgs]):
|
|
19
|
+
"""Tool that executes SQL queries using an injected SqlRunner implementation."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
sql_runner: SqlRunner,
|
|
24
|
+
file_system: Optional[FileSystem] = None,
|
|
25
|
+
custom_tool_name: Optional[str] = None,
|
|
26
|
+
custom_tool_description: Optional[str] = None,
|
|
27
|
+
):
|
|
28
|
+
"""Initialize the tool with a SqlRunner implementation.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
sql_runner: SqlRunner implementation that handles actual query execution
|
|
32
|
+
file_system: FileSystem implementation for saving results (defaults to LocalFileSystem)
|
|
33
|
+
custom_tool_name: Optional custom name for the tool (overrides default "run_sql")
|
|
34
|
+
custom_tool_description: Optional custom description for the tool (overrides default description)
|
|
35
|
+
"""
|
|
36
|
+
self.sql_runner = sql_runner
|
|
37
|
+
self.file_system = file_system or LocalFileSystem()
|
|
38
|
+
self._custom_name = custom_tool_name
|
|
39
|
+
self._custom_description = custom_tool_description
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def name(self) -> str:
|
|
43
|
+
return self._custom_name if self._custom_name else "run_sql"
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def description(self) -> str:
|
|
47
|
+
return (
|
|
48
|
+
self._custom_description
|
|
49
|
+
if self._custom_description
|
|
50
|
+
else "Execute SQL queries against the configured database"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def get_args_schema(self) -> Type[RunSqlToolArgs]:
|
|
54
|
+
return RunSqlToolArgs
|
|
55
|
+
|
|
56
|
+
async def execute(self, context: ToolContext, args: RunSqlToolArgs) -> ToolResult:
|
|
57
|
+
"""Execute a SQL query using the injected SqlRunner."""
|
|
58
|
+
try:
|
|
59
|
+
# Use the injected SqlRunner to execute the query
|
|
60
|
+
df = await self.sql_runner.run_sql(args, context)
|
|
61
|
+
|
|
62
|
+
# Determine query type
|
|
63
|
+
query_type = args.sql.strip().upper().split()[0]
|
|
64
|
+
|
|
65
|
+
if query_type == "SELECT":
|
|
66
|
+
# Handle SELECT queries with results
|
|
67
|
+
if df.empty:
|
|
68
|
+
result = "Query executed successfully. No rows returned."
|
|
69
|
+
ui_component = UiComponent(
|
|
70
|
+
rich_component=DataFrameComponent(
|
|
71
|
+
rows=[],
|
|
72
|
+
columns=[],
|
|
73
|
+
title="Query Results",
|
|
74
|
+
description="No rows returned",
|
|
75
|
+
),
|
|
76
|
+
simple_component=SimpleTextComponent(text=result),
|
|
77
|
+
)
|
|
78
|
+
metadata = {
|
|
79
|
+
"row_count": 0,
|
|
80
|
+
"columns": [],
|
|
81
|
+
"query_type": query_type,
|
|
82
|
+
"results": [],
|
|
83
|
+
}
|
|
84
|
+
else:
|
|
85
|
+
# Convert DataFrame to records
|
|
86
|
+
results_data = df.to_dict("records")
|
|
87
|
+
columns = df.columns.tolist()
|
|
88
|
+
row_count = len(df)
|
|
89
|
+
|
|
90
|
+
# Write DataFrame to CSV file for downstream tools
|
|
91
|
+
file_id = str(uuid.uuid4())[:8]
|
|
92
|
+
filename = f"query_results_{file_id}.csv"
|
|
93
|
+
csv_content = df.to_csv(index=False)
|
|
94
|
+
await self.file_system.write_file(
|
|
95
|
+
filename, csv_content, context, overwrite=True
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Create result text for LLM with truncated results
|
|
99
|
+
results_preview = csv_content
|
|
100
|
+
if len(results_preview) > 1000:
|
|
101
|
+
results_preview = (
|
|
102
|
+
results_preview[:1000]
|
|
103
|
+
+ "\n(Results truncated to 1000 characters. FOR LARGE RESULTS YOU DO NOT NEED TO SUMMARIZE THESE RESULTS OR PROVIDE OBSERVATIONS. THE NEXT STEP SHOULD BE A VISUALIZE_DATA CALL)"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
result = f"{results_preview}\n\nResults saved to file: {filename}\n\n**IMPORTANT: FOR VISUALIZE_DATA USE FILENAME: {filename}**"
|
|
107
|
+
|
|
108
|
+
# Create DataFrame component for UI
|
|
109
|
+
dataframe_component = DataFrameComponent.from_records(
|
|
110
|
+
records=cast(List[Dict[str, Any]], results_data),
|
|
111
|
+
title="Query Results",
|
|
112
|
+
description=f"SQL query returned {row_count} rows with {len(columns)} columns",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
ui_component = UiComponent(
|
|
116
|
+
rich_component=dataframe_component,
|
|
117
|
+
simple_component=SimpleTextComponent(text=result),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
metadata = {
|
|
121
|
+
"row_count": row_count,
|
|
122
|
+
"columns": columns,
|
|
123
|
+
"query_type": query_type,
|
|
124
|
+
"results": results_data,
|
|
125
|
+
"output_file": filename,
|
|
126
|
+
}
|
|
127
|
+
else:
|
|
128
|
+
# For non-SELECT queries (INSERT, UPDATE, DELETE, etc.)
|
|
129
|
+
# The SqlRunner should return a DataFrame with affected row count
|
|
130
|
+
rows_affected = len(df) if not df.empty else 0
|
|
131
|
+
result = (
|
|
132
|
+
f"Query executed successfully. {rows_affected} row(s) affected."
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
metadata = {"rows_affected": rows_affected, "query_type": query_type}
|
|
136
|
+
ui_component = UiComponent(
|
|
137
|
+
rich_component=NotificationComponent(
|
|
138
|
+
type=ComponentType.NOTIFICATION, level="success", message=result
|
|
139
|
+
),
|
|
140
|
+
simple_component=SimpleTextComponent(text=result),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return ToolResult(
|
|
144
|
+
success=True,
|
|
145
|
+
result_for_llm=result,
|
|
146
|
+
ui_component=ui_component,
|
|
147
|
+
metadata=metadata,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
error_message = f"Error executing query: {str(e)}"
|
|
152
|
+
return ToolResult(
|
|
153
|
+
success=False,
|
|
154
|
+
result_for_llm=error_message,
|
|
155
|
+
ui_component=UiComponent(
|
|
156
|
+
rich_component=NotificationComponent(
|
|
157
|
+
type=ComponentType.NOTIFICATION,
|
|
158
|
+
level="error",
|
|
159
|
+
message=error_message,
|
|
160
|
+
),
|
|
161
|
+
simple_component=SimpleTextComponent(text=error_message),
|
|
162
|
+
),
|
|
163
|
+
error=str(e),
|
|
164
|
+
metadata={"error_type": "sql_error"},
|
|
165
|
+
)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Tool for visualizing DataFrame data from CSV files."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Type
|
|
4
|
+
import logging
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from vanna.core.tool import Tool, ToolContext, ToolResult
|
|
9
|
+
from vanna.components import (
|
|
10
|
+
UiComponent,
|
|
11
|
+
ChartComponent,
|
|
12
|
+
NotificationComponent,
|
|
13
|
+
ComponentType,
|
|
14
|
+
SimpleTextComponent,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from .file_system import FileSystem, LocalFileSystem
|
|
18
|
+
from vanna.integrations.plotly import PlotlyChartGenerator
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VisualizeDataArgs(BaseModel):
|
|
24
|
+
"""Arguments for visualize_data tool."""
|
|
25
|
+
|
|
26
|
+
filename: str = Field(description="Name of the CSV file to visualize")
|
|
27
|
+
title: Optional[str] = Field(
|
|
28
|
+
default=None, description="Optional title for the chart"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class VisualizeDataTool(Tool[VisualizeDataArgs]):
|
|
33
|
+
"""Tool that reads CSV files and generates visualizations using dependency injection."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
file_system: Optional[FileSystem] = None,
|
|
38
|
+
plotly_generator: Optional[PlotlyChartGenerator] = None,
|
|
39
|
+
):
|
|
40
|
+
"""Initialize the tool with FileSystem and PlotlyChartGenerator.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
file_system: FileSystem implementation for reading CSV files (defaults to LocalFileSystem)
|
|
44
|
+
plotly_generator: PlotlyChartGenerator for creating Plotly charts (defaults to PlotlyChartGenerator())
|
|
45
|
+
"""
|
|
46
|
+
self.file_system = file_system or LocalFileSystem()
|
|
47
|
+
self.plotly_generator = plotly_generator or PlotlyChartGenerator()
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def name(self) -> str:
|
|
51
|
+
return "visualize_data"
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def description(self) -> str:
|
|
55
|
+
return "Create a visualization from a CSV file. The tool automatically selects an appropriate chart type based on the data."
|
|
56
|
+
|
|
57
|
+
def get_args_schema(self) -> Type[VisualizeDataArgs]:
|
|
58
|
+
return VisualizeDataArgs
|
|
59
|
+
|
|
60
|
+
async def execute(
|
|
61
|
+
self, context: ToolContext, args: VisualizeDataArgs
|
|
62
|
+
) -> ToolResult:
|
|
63
|
+
"""Read CSV file and generate visualization."""
|
|
64
|
+
try:
|
|
65
|
+
logger.info(f"Starting visualization for file: {args.filename}")
|
|
66
|
+
|
|
67
|
+
# Read the CSV file using FileSystem
|
|
68
|
+
csv_content = await self.file_system.read_file(args.filename, context)
|
|
69
|
+
logger.info(f"Read {len(csv_content)} bytes from CSV file")
|
|
70
|
+
|
|
71
|
+
# Parse CSV into DataFrame
|
|
72
|
+
import io
|
|
73
|
+
|
|
74
|
+
df = pd.read_csv(io.StringIO(csv_content))
|
|
75
|
+
logger.info(
|
|
76
|
+
f"Parsed DataFrame with shape {df.shape}, columns: {df.columns.tolist()}, dtypes: {df.dtypes.to_dict()}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Generate title
|
|
80
|
+
title = args.title or f"Visualization of {args.filename}"
|
|
81
|
+
|
|
82
|
+
# Generate chart using PlotlyChartGenerator
|
|
83
|
+
logger.info("Generating chart...")
|
|
84
|
+
chart_dict = self.plotly_generator.generate_chart(df, title)
|
|
85
|
+
logger.info(
|
|
86
|
+
f"Chart generated, type: {type(chart_dict)}, keys: {list(chart_dict.keys()) if isinstance(chart_dict, dict) else 'N/A'}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Create result message
|
|
90
|
+
row_count = len(df)
|
|
91
|
+
col_count = len(df.columns)
|
|
92
|
+
result = f"Created visualization from '{args.filename}' ({row_count} rows, {col_count} columns)."
|
|
93
|
+
|
|
94
|
+
# Create ChartComponent
|
|
95
|
+
logger.info("Creating ChartComponent...")
|
|
96
|
+
chart_component = ChartComponent(
|
|
97
|
+
chart_type="plotly",
|
|
98
|
+
data=chart_dict,
|
|
99
|
+
title=title,
|
|
100
|
+
config={
|
|
101
|
+
"data_shape": {"rows": row_count, "columns": col_count},
|
|
102
|
+
"source_file": args.filename,
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
logger.info("ChartComponent created successfully")
|
|
106
|
+
|
|
107
|
+
logger.info("Creating ToolResult...")
|
|
108
|
+
tool_result = ToolResult(
|
|
109
|
+
success=True,
|
|
110
|
+
result_for_llm=result,
|
|
111
|
+
ui_component=UiComponent(
|
|
112
|
+
rich_component=chart_component,
|
|
113
|
+
simple_component=SimpleTextComponent(text=result),
|
|
114
|
+
),
|
|
115
|
+
metadata={
|
|
116
|
+
"filename": args.filename,
|
|
117
|
+
"rows": row_count,
|
|
118
|
+
"columns": col_count,
|
|
119
|
+
"chart": chart_dict,
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
logger.info("ToolResult created successfully")
|
|
123
|
+
return tool_result
|
|
124
|
+
|
|
125
|
+
except FileNotFoundError as e:
|
|
126
|
+
logger.error(f"File not found: {args.filename}", exc_info=True)
|
|
127
|
+
error_message = f"File not found: {args.filename}"
|
|
128
|
+
return ToolResult(
|
|
129
|
+
success=False,
|
|
130
|
+
result_for_llm=error_message,
|
|
131
|
+
ui_component=UiComponent(
|
|
132
|
+
rich_component=NotificationComponent(
|
|
133
|
+
type=ComponentType.NOTIFICATION,
|
|
134
|
+
level="error",
|
|
135
|
+
message=error_message,
|
|
136
|
+
),
|
|
137
|
+
simple_component=SimpleTextComponent(text=error_message),
|
|
138
|
+
),
|
|
139
|
+
error=str(e),
|
|
140
|
+
metadata={"error_type": "file_not_found"},
|
|
141
|
+
)
|
|
142
|
+
except pd.errors.ParserError as e:
|
|
143
|
+
logger.error(f"CSV parse error for {args.filename}", exc_info=True)
|
|
144
|
+
error_message = f"Failed to parse CSV file '{args.filename}': {str(e)}"
|
|
145
|
+
return ToolResult(
|
|
146
|
+
success=False,
|
|
147
|
+
result_for_llm=error_message,
|
|
148
|
+
ui_component=UiComponent(
|
|
149
|
+
rich_component=NotificationComponent(
|
|
150
|
+
type=ComponentType.NOTIFICATION,
|
|
151
|
+
level="error",
|
|
152
|
+
message=error_message,
|
|
153
|
+
),
|
|
154
|
+
simple_component=SimpleTextComponent(text=error_message),
|
|
155
|
+
),
|
|
156
|
+
error=str(e),
|
|
157
|
+
metadata={"error_type": "csv_parse_error"},
|
|
158
|
+
)
|
|
159
|
+
except ValueError as e:
|
|
160
|
+
logger.error(f"Visualization error for {args.filename}", exc_info=True)
|
|
161
|
+
error_message = f"Cannot visualize data: {str(e)}"
|
|
162
|
+
return ToolResult(
|
|
163
|
+
success=False,
|
|
164
|
+
result_for_llm=error_message,
|
|
165
|
+
ui_component=UiComponent(
|
|
166
|
+
rich_component=NotificationComponent(
|
|
167
|
+
type=ComponentType.NOTIFICATION,
|
|
168
|
+
level="error",
|
|
169
|
+
message=error_message,
|
|
170
|
+
),
|
|
171
|
+
simple_component=SimpleTextComponent(text=error_message),
|
|
172
|
+
),
|
|
173
|
+
error=str(e),
|
|
174
|
+
metadata={"error_type": "visualization_error"},
|
|
175
|
+
)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logger.error(
|
|
178
|
+
f"Unexpected error creating visualization for {args.filename}",
|
|
179
|
+
exc_info=True,
|
|
180
|
+
)
|
|
181
|
+
error_message = f"Error creating visualization: {str(e)}"
|
|
182
|
+
return ToolResult(
|
|
183
|
+
success=False,
|
|
184
|
+
result_for_llm=error_message,
|
|
185
|
+
ui_component=UiComponent(
|
|
186
|
+
rich_component=NotificationComponent(
|
|
187
|
+
type=ComponentType.NOTIFICATION,
|
|
188
|
+
level="error",
|
|
189
|
+
message=error_message,
|
|
190
|
+
),
|
|
191
|
+
simple_component=SimpleTextComponent(text=error_message),
|
|
192
|
+
),
|
|
193
|
+
error=str(e),
|
|
194
|
+
metadata={"error_type": "general_error"},
|
|
195
|
+
)
|
vanna/utils/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Web components for Vanna Agents.
|
|
3
|
+
|
|
4
|
+
This module provides web components built with Lit that can be embedded
|
|
5
|
+
in web applications to provide rich UI for Vanna agent interactions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_component_files() -> Dict[str, Path]:
|
|
14
|
+
"""Get paths to all web component files."""
|
|
15
|
+
component_dir = Path(__file__).parent
|
|
16
|
+
return {
|
|
17
|
+
"js": component_dir / "index.js",
|
|
18
|
+
"css": component_dir / "style.css",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_component_html() -> str:
|
|
23
|
+
"""Get HTML template for including components."""
|
|
24
|
+
files = get_component_files()
|
|
25
|
+
|
|
26
|
+
html = """
|
|
27
|
+
<!DOCTYPE html>
|
|
28
|
+
<html>
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="utf-8">
|
|
31
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
32
|
+
<title>Vanna AI Chat</title>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<vanna-chat title="Vanna AI Assistant"></vanna-chat>
|
|
36
|
+
<script type="module" src="{js_file}"></script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
""".format(js_file=files["js"].name)
|
|
40
|
+
|
|
41
|
+
return html
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["get_component_files", "get_component_html"]
|