Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LumenAI Views in Sidebar #610

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lumen/ai/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from panel.chat import ChatInterface
from panel.pane import HTML
from panel.template import BaseTemplate
from panel.viewable import Viewer
from pydantic import BaseModel, create_model
from pydantic.fields import FieldInfo
Expand Down Expand Up @@ -56,6 +57,8 @@ class Agent(Viewer):

llm = param.ClassSelector(class_=Llm)

template = param.ClassSelector(class_=BaseTemplate)

system_prompt = param.String()

response_model = param.ClassSelector(class_=BaseModel, is_instance=False)
Expand Down Expand Up @@ -379,7 +382,7 @@ class LumenBaseAgent(Agent):
user = param.String(default="Lumen")

def _render_lumen(self, component: Component, message: pn.chat.ChatMessage = None):
out = LumenOutput(component=component)
out = LumenOutput(component=component, template=self.template)
message_kwargs = dict(value=out, user=self.user)
self.interface.stream(message=message, **message_kwargs, replace=True)

Expand Down Expand Up @@ -506,7 +509,7 @@ class SQLAgent(LumenBaseAgent):

def _render_sql(self, query):
pipeline = memory['current_pipeline']
out = SQLOutput(component=pipeline, spec=query)
out = SQLOutput(component=pipeline, spec=query, template=self.template)
self.interface.stream(out, user="SQL", replace=True)

@retry_llm_output()
Expand Down
37 changes: 25 additions & 12 deletions lumen/ai/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@

from panel import bind
from panel.chat import ChatInterface, ChatMessage
from panel.layout import Column, FlexBox, Tabs
from panel.layout import (
Column, FlexBox, Row, Tabs,
)
from panel.pane import HTML, Markdown
from panel.template import FastListTemplate
from panel.viewable import Viewer
from panel.widgets import Button, FileDownload
from pydantic import create_model
Expand Down Expand Up @@ -40,6 +43,8 @@
"Show it to me as a scatter plot."
]

_SIDEBAR_HTML = HTML("")


class Assistant(Viewer):
"""
Expand Down Expand Up @@ -121,12 +126,23 @@ def download_notebook():
self._logs = ChatLogs(filename=logs_filename)
interface.post_hook = on_message

header = Row()
sidebar = Column(sizing_mode="stretch_both")
self._template = FastListTemplate(
title="Lumen.ai",
sidebar_width=600,
sidebar=[sidebar, _SIDEBAR_HTML],
main=[interface],
header=[header],
collapsed_sidebar=True,
)

llm = llm or self.llm
instantiated = []
for agent in agents or self.agents:
if not isinstance(agent, Agent):
kwargs = {"llm": llm} if agent.llm is None else {}
agent = agent(interface=interface, **kwargs)
agent = agent(interface=interface, template=self._template, **kwargs)
instantiated.append(agent)

super().__init__(llm=llm, agents=instantiated, interface=interface, logs_filename=logs_filename, **params)
Expand All @@ -145,15 +161,15 @@ def download_notebook():

notebook_button = FileDownload(
icon="notebook",
button_type="success",
button_type="light",
button_style="outline",
callback=download_notebook,
filename="Lumen_ai.ipynb",
sizing_mode="stretch_width",
)

self._controls = Column(
notebook_button, *self.sidebar_widgets, self._current_agent, Tabs(("Memory", memory))
margin=(15, 0, 0, 0),
)
header.extend([
self._current_agent, notebook_button, *self.sidebar_widgets,
])

def _add_suggestions_to_footer(self, suggestions: list[str], inplace: bool = True):
async def hide_suggestions(_=None):
Expand Down Expand Up @@ -394,8 +410,5 @@ async def invoke(self, messages: list | str) -> str:
print("\033[92mDONE\033[0m", "\n\n")
return result

def controls(self):
return self._controls

def __panel__(self):
return self.interface
return self._template
98 changes: 70 additions & 28 deletions lumen/ai/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import panel as pn
import panel.config as _pn_config
import param
import yaml

from panel.io.resources import CSS_URLS
from panel.template.base import BaseTemplate
from panel.viewable import Viewer

from ..base import Component
Expand All @@ -11,13 +14,16 @@
from ..views.base import Table
from .memory import memory

if CSS_URLS["font-awesome"] not in _pn_config.css_files:
_pn_config.css_files.append(CSS_URLS["font-awesome"])

class LumenOutput(Viewer):

active = param.Integer(default=1)
class LumenOutput(Viewer):

component = param.ClassSelector(class_=Component)

template = param.ClassSelector(class_=BaseTemplate)

spec = param.String()

language = "yaml"
Expand All @@ -27,8 +33,56 @@ def __init__(self, **params):
component_spec = params['component'].to_spec()
params['spec'] = yaml.safe_dump(component_spec)
super().__init__(**params)
view_button = pn.widgets.Button(
name="View Output",
button_type="primary",
stylesheets=[
"""
:host(.solid) .bk-btn.bk-btn-primary {
position: relative;
background-color: #1a1a1a;
font-size: 1.5em;
text-align: left;
padding-left: 45px;
padding-bottom: 10px;
font-weight: 350;
width: fit-content;
}
.bk-btn.bk-btn-primary::before {
content: "\\f56e"; /* Unicode for Font Awesome code icon */
font-family: "Font Awesome 5 Free"; /* Specify the Font Awesome font */
font-weight: 600; /* Ensure the correct font weight for the icon */
-webkit-text-stroke: 1px #1a1a1a;
position: absolute;
top: 50%;
left: 8px;
transform: translateY(-50%) rotateY(180deg);
width: 100%;
text-align: right;
font-size: 1em;
color: white;
}
.bk-btn.bk-btn-primary::after {
content: "Click to open content in the sidebar";
display: block;
font-size: 50%;
text-align: left;
font-weight: 50%;
}
"""
],
height=75,
on_click=self._view_content,
)
self._view_button = view_button

placeholder = pn.Column(sizing_mode="stretch_width", min_height=500)
placeholder.objects = [pn.pane.ParamMethod(self._render_component, inplace=True)]

divider = pn.layout.Divider(width=10, height=10)

code_editor = pn.widgets.CodeEditor(
value=self.spec, language=self.language, sizing_mode="stretch_both",
value=self.spec, language=self.language, min_height=350, sizing_mode="stretch_both",
)
code_editor.link(self, bidirectional=True, value='spec')
copy_icon = pn.widgets.ButtonIcon(
Expand Down Expand Up @@ -56,29 +110,20 @@ def __init__(self, **params):
""",
)
icons = pn.Row(copy_icon, download_icon)
code_col = pn.Column(code_editor, icons, sizing_mode="stretch_both")
placeholder = pn.Column(sizing_mode="stretch_width")
self._tabs = pn.Tabs(
("Code", code_col),
("Output", placeholder),
styles={'min-width': "100%"},
height=700,
active=1
)
self._tabs.link(self, bidirectional=True, active='active')
placeholder.objects = [
pn.pane.ParamMethod(self._render_component, inplace=True)
]

@param.depends('spec', 'active')
self._content = pn.Column(placeholder, divider, code_editor, icons, sizing_mode="stretch_both")
view_button.param.trigger("clicks")

def _view_content(self, event):
self.template.sidebar[0].objects = [self._content]
self.template.sidebar[-1].object = "<script> openNav(); </script>"

@param.depends('spec')
async def _render_component(self):
yield pn.indicators.LoadingSpinner(
value=True, name="Rendering component...", height=50, width=50
)

if self.active != 1:
return

# store the spec in the cache instead of memory to save tokens
memory["current_spec"] = self.spec
try:
Expand All @@ -95,7 +140,7 @@ async def _render_component(self):
download_pane = download.__panel__()
download_pane.sizing_mode = 'fixed'
download_pane.styles = {'position': 'absolute', 'right': '-50px'}
output = pn.Column(download_pane, table)
output = pn.Column(download_pane, table, sizing_mode='stretch_both')
else:
output = self.component.__panel__()
yield output
Expand All @@ -108,8 +153,7 @@ async def _render_component(self):
)

def __panel__(self):
return self._tabs

return self._view_button

def __repr__(self):
return self.spec
Expand All @@ -119,20 +163,18 @@ class SQLOutput(LumenOutput):

language = "sql"

@param.depends('spec', 'active')
@param.depends('spec')
async def _render_component(self):
yield pn.indicators.LoadingSpinner(
value=True, name="Executing SQL query...", height=50, width=50
)
if self.active != 1:
return

pipeline = self.component
pipeline.source = pipeline.source.create_sql_expr_source({pipeline.table: self.spec})
try:
table = Table(
pipeline=pipeline, pagination='remote',
height=458, page_size=12
height=458, page_size=12, sizing_mode='stretch_both'
)
download = Download(
view=table, hide=False, filename=f'{self.component.table}',
Expand All @@ -152,4 +194,4 @@ async def _render_component(self):
)

def __panel__(self):
return self._tabs
return self._view_button
4 changes: 3 additions & 1 deletion lumen/sources/duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class DuckDBSource(BaseSQLSource):

uri = param.String(doc="The URI of the DuckDB database")

sql_expr = param.String(default='SELECT * FROM "{table}"', doc="""
sql_expr = param.String(default='SELECT * FROM {table}', doc="""
The SQL expression to execute.""")

tables = param.ClassSelector(class_=(list, dict), doc="""
Expand Down Expand Up @@ -82,6 +82,8 @@ def get_tables(self):
def get_sql_expr(self, table: str):
if isinstance(self.tables, dict):
table = self.tables[table]
if 'read_' not in table:
table = f'"{table}"'
if 'select ' in table.lower():
sql_expr = table
else:
Expand Down
Loading