feat: cockpit-style model plaza with search, category tabs, provider sidebar
New layout: - Search bar (top) with fuzzy name/description match - Category tabs (wrapping, single-select) - Provider sidebar (left, single-select) - Card grid (right, sorted by name, refreshed on filter change) - Both category/provider unselected = all published models Added get_plaza_models() for flat filtered model listing. Supports combined filtering: provider + category + search.
This commit is contained in:
parent
68dd92a353
commit
1c25542e72
@ -26,6 +26,7 @@ from .utils import (
|
||||
_warm_llmid_cache,
|
||||
get_llmid_cached,
|
||||
invalidate_llmid_cache,
|
||||
get_plaza_models,
|
||||
)
|
||||
|
||||
from .llmclient import (
|
||||
@ -163,6 +164,7 @@ def load_llmage():
|
||||
env.keling_token = keling_token
|
||||
env.llm_query_price = llm_query_price
|
||||
env.get_llms_by_catelog_to_customer = get_llms_by_catelog_to_customer
|
||||
env.get_plaza_models = get_plaza_models
|
||||
env.backup_accounted_llmusage = backup_accounted_llmusage
|
||||
env.read_ioinfo_content = read_ioinfo_content
|
||||
env.get_llmusage_by_id = get_llmusage_by_id
|
||||
|
||||
@ -581,6 +581,33 @@ def _file_mime(filepath):
|
||||
mime, _ = _mimetypes.guess_type(filepath)
|
||||
return mime or 'application/octet-stream'
|
||||
|
||||
async def get_plaza_models(providerid='', catelogid='', search=''):
|
||||
"""Flat model list for cockpit plaza: filterable by provider/catalog/search, sorted by name."""
|
||||
env = ServerEnv()
|
||||
data = await get_llms_by_catelog_to_customer(
|
||||
catelogid=catelogid if catelogid else None,
|
||||
orderby='a.name'
|
||||
)
|
||||
result = []
|
||||
for cate in data:
|
||||
for llm in cate.llms:
|
||||
if providerid and llm.providerid != providerid:
|
||||
continue
|
||||
if search:
|
||||
sl = search.lower()
|
||||
if sl not in (llm.name or '').lower() and sl not in (llm.description or '').lower():
|
||||
continue
|
||||
result.append({
|
||||
'id': llm.id, 'name': llm.name, 'model': llm.model,
|
||||
'description': llm.description or '', 'iconid': llm.iconid,
|
||||
'providerid': llm.providerid,
|
||||
'provider_name': getattr(llm, 'provider_name', getattr(llm, 'orgname', '')),
|
||||
'catelog_id': getattr(llm, 'catelog_id', ''),
|
||||
'catelogname': getattr(llm, 'catelogname', ''),
|
||||
'pricing_display': getattr(llm, 'pricing_display', []),
|
||||
})
|
||||
return result
|
||||
|
||||
ServerEnv().base64 = _base64
|
||||
ServerEnv().mimetypes = _mimetypes
|
||||
ServerEnv().os = _os
|
||||
|
||||
33
wwwroot/api/get_plaza_models.dspy
Normal file
33
wwwroot/api/get_plaza_models.dspy
Normal file
@ -0,0 +1,33 @@
|
||||
import json
|
||||
|
||||
providerid = (params_kw or {}).get('providerid', '')
|
||||
catelogid = (params_kw or {}).get('catelogid', '')
|
||||
search = (params_kw or {}).get('search', '').strip()
|
||||
|
||||
data = await request._run_ns.get_llms_by_catelog_to_customer(
|
||||
catelogid=catelogid if catelogid else None,
|
||||
orderby='a.name'
|
||||
)
|
||||
|
||||
result = []
|
||||
for cate in data:
|
||||
for llm in cate.llms:
|
||||
if providerid and llm.providerid != providerid:
|
||||
continue
|
||||
if search and search.lower() not in (llm.name or '').lower():
|
||||
if search.lower() not in (llm.description or '').lower():
|
||||
continue
|
||||
result.append({
|
||||
'id': llm.id,
|
||||
'name': llm.name,
|
||||
'model': llm.model,
|
||||
'description': llm.description or '',
|
||||
'iconid': llm.iconid,
|
||||
'providerid': llm.providerid,
|
||||
'provider_name': getattr(llm, 'provider_name', getattr(llm, 'orgname', '')),
|
||||
'catelog_id': getattr(llm, 'catelog_id', ''),
|
||||
'catelogname': getattr(llm, 'catelogname', ''),
|
||||
'pricing_display': getattr(llm, 'pricing_display', []),
|
||||
})
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
@ -1,122 +1,169 @@
|
||||
{% set active_provider = params_kw.get('providerid', '') %}
|
||||
{% set active_catelog = params_kw.get('catelogid', '') %}
|
||||
{% set active_search = params_kw.get('search', '') %}
|
||||
{% set catelogs = get_llmcatelogs() %}
|
||||
{% set providers = get_llms_sort_by_provider() %}
|
||||
{% set providers = get_llmproviders() %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "plaza-header",
|
||||
"width": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"otext": "模型广场",
|
||||
"i18n": true,
|
||||
"halign": "left"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "探索和使用各类AI模型",
|
||||
"i18n": true,
|
||||
"halign": "left",
|
||||
"wrap": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"css": "plaza-view-switcher",
|
||||
"cheight": 3
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"id": "btn_by_catelog",
|
||||
"options": {
|
||||
"label": "按分类",
|
||||
"css": "plaza-view-btn plaza-view-active",
|
||||
"cwidth": 15
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "document.getElementById('plaza_view_provider').style.display='none'; document.getElementById('plaza_view_catelog').style.display='flex'; var a=bricks.getWidgetById('btn_by_catelog',bricks.app); var b=bricks.getWidgetById('btn_by_provider',bricks.app); if(a)a.dom_element.classList.add('plaza-view-active'); if(b)b.dom_element.classList.remove('plaza-view-active');"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"id": "btn_by_provider",
|
||||
"options": {
|
||||
"label": "按供应商",
|
||||
"css": "plaza-view-btn",
|
||||
"cwidth": 15
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "document.getElementById('plaza_view_catelog').style.display='none'; document.getElementById('plaza_view_provider').style.display='flex'; var a=bricks.getWidgetById('btn_by_catelog',bricks.app); var b=bricks.getWidgetById('btn_by_provider',bricks.app); if(a)a.dom_element.classList.remove('plaza-view-active'); if(b)b.dom_element.classList.add('plaza-view-active');"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "plaza_view_catelog",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"url": "{{entire_url('show_llms.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "plaza_view_provider",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"url": "{{entire_url('show_llms_by_providers.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "filler", "width": "100%", "height": "100%", "gap": "8px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "plaza-header", "width": "100%"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title2",
|
||||
"options": {"otext": "模型广场", "i18n": true, "halign": "left"}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "cheight": 4, "gap": "6px", "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Input",
|
||||
"id": "plaza_search_input",
|
||||
"options": {
|
||||
"cwidth": 40,
|
||||
"cheight": 3,
|
||||
"placeholder": "搜索模型名称或描述...",
|
||||
"text": "{{active_search}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"id": "btn_search",
|
||||
"options": {"label": "搜索", "css": "plaza-search-btn", "cwidth": 8, "cheight": 3},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self", "event": "click", "actiontype": "script", "target": "self",
|
||||
"script": "var inp=bricks.getWidgetById('plaza_search_input',bricks.app);var q=inp?inp.dom_element.querySelector('input').value:'';var u='{{entire_url('model_plaza.ui')}}?providerid={{active_provider}}&catelogid={{active_catelog}}&search='+encodeURIComponent(q);bricks.app.navigate_to(u);"
|
||||
}
|
||||
]
|
||||
},
|
||||
{% if active_search %}
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "清除", "css": "plaza-clear-btn", "cwidth": 6, "cheight": 3},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "-@",
|
||||
"options": {"url": "{{entire_url('model_plaza.ui')}}?providerid={{active_provider}}&catelogid={{active_catelog}}"}
|
||||
}
|
||||
]
|
||||
},
|
||||
{% endif %}
|
||||
{"widgettype": "Filler"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"css": "plaza-catelog-bar", "width": "100%", "wrap": true, "gap": "4px", "padding": "4px 0"},
|
||||
"binds": [],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"id": "cat_all",
|
||||
"options": {
|
||||
"label": "全部",
|
||||
"css": "{{'plaza-cat-btn selected' if not active_catelog else 'plaza-cat-btn'}}",
|
||||
"cwidth": 8, "cheight": 2
|
||||
},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "-@",
|
||||
"options": {"url": "{{entire_url('model_plaza.ui')}}?providerid={{active_provider}}&search={{active_search}}"}
|
||||
}]
|
||||
}
|
||||
{% for c in catelogs %}
|
||||
,{
|
||||
"widgettype": "Button",
|
||||
"id": "cat_{{c.id}}",
|
||||
"options": {
|
||||
"label": "{{c.name}}",
|
||||
"css": "{{'plaza-cat-btn selected' if active_catelog==c.id else 'plaza-cat-btn'}}",
|
||||
"cwidth": 10, "cheight": 2
|
||||
},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "-@",
|
||||
"options": {"url": "{{entire_url('model_plaza.ui')}}?providerid={{active_provider}}&catelogid={{c.id}}&search={{active_search}}"}
|
||||
}]
|
||||
}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "height": "100%", "flex": "1", "gap": "8px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "plaza-sidebar", "cwidth": 20},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title6",
|
||||
"options": {"otext": "供应商", "i18n": true, "marginBottom": "4px"}
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {"css": "filler", "flex": "1"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"gap": "2px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"id": "prov_all",
|
||||
"options": {
|
||||
"label": "全部供应商",
|
||||
"css": "{{'plaza-prov-btn selected' if not active_provider else 'plaza-prov-btn'}}",
|
||||
"cwidth": 18
|
||||
},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "-@",
|
||||
"options": {"url": "{{entire_url('model_plaza.ui')}}?catelogid={{active_catelog}}&search={{active_search}}"}
|
||||
}]
|
||||
}
|
||||
{% for p in providers %}
|
||||
,{
|
||||
"widgettype": "Button",
|
||||
"id": "prov_{{p.providerid}}",
|
||||
"options": {
|
||||
"label": "{{p.orgname}}",
|
||||
"css": "{{'plaza-prov-btn selected' if active_provider==p.providerid else 'plaza-prov-btn'}}",
|
||||
"cwidth": 18
|
||||
},
|
||||
"binds": [{
|
||||
"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "-@",
|
||||
"options": {"url": "{{entire_url('model_plaza.ui')}}?providerid={{p.providerid}}&catelogid={{active_catelog}}&search={{active_search}}"}
|
||||
}]
|
||||
}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "RefreshWidget",
|
||||
"id": "plaza_cards",
|
||||
"options": {
|
||||
"period_seconds": 0,
|
||||
"url": "{{entire_url('plaza_model_cards.ui')}}?providerid={{active_provider}}&catelogid={{active_catelog}}&search={{active_search}}",
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
107
wwwroot/plaza_model_cards.ui
Normal file
107
wwwroot/plaza_model_cards.ui
Normal file
@ -0,0 +1,107 @@
|
||||
{% set providerid = params_kw.get('providerid', '') %}
|
||||
{% set catelogid = params_kw.get('catelogid', '') %}
|
||||
{% set search = params_kw.get('search', '') %}
|
||||
{% set data = get_plaza_models(providerid, catelogid, search) %}
|
||||
{% set ns = namespace(first=true) %}
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "DynamicColumn",
|
||||
"options": {
|
||||
"css": "plaza-grid",
|
||||
"width": "100%",
|
||||
"col_cwidth": 25,
|
||||
"col_cgap": 1
|
||||
},
|
||||
"subwidgets": [
|
||||
{% for llm in data %}
|
||||
{% if not ns.first %},{% endif %}
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {
|
||||
"css": "card plaza-card",
|
||||
"cwidth": 25,
|
||||
"cheight": 16
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"cheight": 2},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {"rate": 1.5, "url": "{{entire_url('/appbase/show_icon.dspy')}}?id={{llm.iconid}}"}
|
||||
},
|
||||
{
|
||||
"widgettype": "Title6",
|
||||
"options": {"text": "{{llm.name}}"}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {"css": "card-body-scroll", "flex": "1"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": {{json.dumps(llm.description, ensure_ascii=False)}},
|
||||
"wrap": true,
|
||||
"halign": "left"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "pricing-box", "cheight": 4},
|
||||
"subwidgets": [
|
||||
{% for pt in llm.pricing_display %}
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": {{json.dumps(pt, ensure_ascii=False)}},
|
||||
"wrap": true,
|
||||
"halign": "left",
|
||||
"css": "pricing-text"
|
||||
}
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
],
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "PopupWindow",
|
||||
"popup_options": {
|
||||
"title": "{{llm.name}}",
|
||||
{% if int(params_kw._is_mobile or 0) %}
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
{% else %}
|
||||
"width": "40%",
|
||||
"height": "85%"
|
||||
{% endif %}
|
||||
},
|
||||
"options": {
|
||||
"params": {"id": "{{llm.id}}"},
|
||||
"url": "{{entire_url('./llm_dialog.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{% set ns.first = false %}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user