903 Commits

Author SHA1 Message Date
aad779b14e fix(sync): load_product_category_product SQL缺少providerid字段 — 产品同步providerid永远为空 2026-08-05 13:42:30 +08:00
31c8fde336 fix(balance): replace GETDEL with GET+DEL in Lua scripts for Redis 6.0 compat
GETDEL requires Redis 6.2+; server runs 6.0.16, so every finalize_balance
and refund_balance call failed silently ('Unknown Redis command called
from Lua script'). Consequences observed under concurrent load test:
- reserve:* keys leaked until 600s TTL instead of being released at finalize
- balance:{org} never reconciled with actual cost (over-deduction by
  max_cost-actual accumulated per call)
- FAILED-request refunds silently lost

GET+DEL inside a Lua script executes atomically (single-threaded),
so the pop semantics are unchanged.
2026-08-05 12:05:30 +08:00
e3f61f446d feat(balance): extend atomic balance reserve to async+sync+product paths
- balance.py: module-level redis.asyncio singleton (works in all
  processes without env.redis injection), centralized tpac/self-org
  skip policy, userid param, extend_reserve, invalidate_balance_cache,
  finalize updates max_cost on cold start (no reserve)
- llmclient.py: unified reserve injection in _inference_generator
  (covers all 22 dspy entries); fix luid mismatch (reserve key ==
  llmusage id == finalize key); stream exception refund
- syncinference.py / asyncinference.py: luid reuse + failure refunds
  (submit failure, outer exception, query_task_status FAILED)
- product_interface.py: reserve + refund on product stream/non-stream
- accounting.py: finalize after llm_accounting (was missing for
  direct llmage accounting path -> reserve leaked until TTL)
- init.py: lambda signatures synced
- v1/chat/completions/index.dspy: pass userid to reserve
2026-08-04 18:35:53 +08:00
89e6d7cb25 fix(publish): call sync_llm_product outside transaction block (stale read before commit) 2026-08-04 13:26:29 +08:00
0c4bc661eb fix(publish): auto-sync llm to product on publish via env.sync_llm_product 2026-08-04 12:36:04 +08:00
b97a14d41d bugfix 2026-08-03 14:58:52 +08:00
7fd2c5622d bugfix 2026-08-03 14:36:03 +08:00
02178cd06e bugfix 2026-08-03 14:34:17 +08:00
dff5a681a0 bugfix 2026-08-03 13:55:56 +08:00
e5d27bc6bd fix: retry write_llmusage with *1,*2 on duplicate; gen() catch+refund but yield error not raise 2026-07-31 16:48:40 +08:00
670325e8eb fix: move refund to uapi_request except, remove gen() try/except that re-raised causing 500
gen() try/except re-raised internal errors from uapi_request → 500 response.
Moved refund_balance to uapi_request's except block where errors are properly
handled (yield error, normal generator exit).
2026-07-31 16:39:43 +08:00
9da343b2e1 fix: use uuid4 instead of getID() in DSPY to avoid nanoid collision under concurrency
Two getID() calls per request (DSPY + uapi_request) under 200 concurrent
caused nanoid random state corruption → 34% duplicate IDs → IntegrityError.
uuid4 uses OS entropy, independent of nanoid state.
2026-07-31 16:30:49 +08:00
8c77f552a8 fix: remove stray tab causing IndentationError in chat/completions/index.dspy 2026-07-31 15:51:24 +08:00
4955e29f7d fix: re-export reserve/finalize/refund_balance from balance.py in utils.py (init.py imports them from .utils) 2026-07-31 15:48:43 +08:00
a02cd09459 fix: Redis connection error now returns no_redis fallback instead of 429 2026-07-31 15:42:51 +08:00
6351ee9b56 feat: Redis atomic balance reservation — prevent concurrent overspend
- balance.py: reserve_balance/finalize_balance/refund_balance with Lua
- utils.py: get_model_max_cost from llmusage history, update_model_max_cost
- init.py: register reserve_balance, finalize_balance, refund_balance on env
- chat/completions/index.dspy: reserve before inference, refund on exception
- DB migration: llm.max_cost DECIMAL(10,4) for historical max charge
2026-07-31 14:57:17 +08:00
514b674f04 fix(model_plaza): enable flexbox on parent VBox for DimensionFilter height 2026-07-31 11:38:10 +08:00
a76a46d8bc fix: card col_cwidth/cwidth 25->20 for narrower DimensionFilter display area 2026-07-30 18:28:01 +08:00
703886c09e fix(model_plaza): add DimensionFilter param name mapping for catelogid/providerid 2026-07-30 16:49:24 +08:00
c058a2c8ba refactor(model_plaza): rewrite with DimensionFilter widget, add category/provider DSPY endpoints
- Create plaza_categories.dspy: returns {rows: [{id, name}]} from get_llmcatelogs()
- Create plaza_providers.dspy: returns {rows: [{id, name}]} from get_llmproviders()
- Rewrite model_plaza.ui: replaced old button/grid layout with DimensionFilter widget
- Update plaza_model_cards.ui: accept both new (h_id/v_id/keyword) and old (catelogid/providerid/search) param names
- Update sage/load_path.py: register new DSPY endpoints
2026-07-30 16:13:40 +08:00
c63fb329f4 fix: unescaped double quote in SearchBar script breaks JSON (url:" -> url:') 2026-07-30 11:25:40 +08:00
4db32670e9 fix: getWidgetById from bricks.app root, clear_widgets+widgetBuild for nav
getWidgetById searches DOWNWARD (children), not upward. For ancestor
sage_main_content, must search from bricks.app global root.
Using clear_widgets()+widgetBuild instead of nonexistent set_url().
2026-07-30 11:20:19 +08:00
8d8f9b58a5 fix: use urlwidget actiontype (not script) for internal navigation
Script-based getWidgetById+widgetBuild was async without await,
causing black screen. urlwidget actiontype is Bricks' built-in
navigation mechanism that properly handles async rendering.
2026-07-30 11:17:21 +08:00
1623c891ab fix: use bricks.getWidgetById(sage_main_content) instead of location.href
All filter buttons now internally navigate via:
  bricks.getWidgetById('sage_main_content', bricks.app)
  → clear_widgets() → widgetBuild(urlwidget)
This keeps model_plaza within the app shell instead of standalone page.
2026-07-30 11:11:33 +08:00
21bf30d2a6 fix: add finally block to write_llmusage — recover from GeneratorExit on client disconnect
When client disconnects mid-stream, Python GC kills the generator with
GeneratorExit (BaseException subclass), which is NOT caught by except Exception.
write_llmusage at line 93 was never reached, causing ~16% usage records lost.
finally block ensures llmusage is written even on GeneratorExit.
2026-07-30 10:49:06 +08:00
06e14af6ab test: load_test.py upgraded — 4min×3(50/100/200) + HTTP status + host CPU/MEM monitoring 2026-07-29 18:33:14 +08:00
9c363bc617 fix: VBox flex:1 wrapper for card scroll, robust search keyword extraction 2026-07-29 18:15:23 +08:00
1a2aa0c0bb fix: RefreshWidget period_seconds:0 infinite loop -> urlwidget 2026-07-29 17:26:58 +08:00
4f0a8a4c2e fix: cwidth:25 cheight:16 card-body-scroll — match old working layout 2026-07-29 17:11:40 +08:00
7aa1efc6d8 revert: back to server-side Jinja2 rendering, Tabular AJAX had 401 session issues
Tabular widget makes client-side HTTP calls to data_url DSPY which
fail with 401 because session cookie not passed in AJAX requests.
Server-side Jinja2 loop avoids this entirely — all model data rendered
in initial page load.
2026-07-29 16:04:07 +08:00
15b4ad38fb fix: {total,data} -> {total,rows} — DataViewer reads d.rows not d.data 2026-07-29 15:29:38 +08:00
960edc2581 fix: DSPY cate.llms->cate['llms'], return {total,data} for DataViewer
DataViewer/PageDataLoader expects {total:N, data:[...]} format.
Bare array causes d.total=undefined → NaN → infinite paging loop.
Also fixes same cate.llms attribute access bug from before.
2026-07-29 15:23:33 +08:00
659af09a89 feat: Tabular seamless infinite scroll instead of page buttons
Tabular handles paging client-side — scroll to bottom auto-loads more.
Record_view renders each model as a card (icon + name + description).
Removed all server-side pagination logic from the template.
2026-07-29 15:13:13 +08:00
10280f60f1 fix: trailing comma when no pagination block rendered 2026-07-29 15:00:38 +08:00
e9dc4208e0 chore: remove .swp, update gitignore 2026-07-29 14:59:28 +08:00
063c0a46bb fix: use relative URL for location.href to stay in workspace 2026-07-29 14:59:17 +08:00
31119554d4 fix: location.href for all nav, no mixed JS/server state, card width:100%
- All filter buttons use location.href with fully server-constructed URLs
  No more mixing {{active_provider}} (stale server var) with dynamic JS state
- SearchBar, category, provider, pagination all use location.href pattern
- Card uses width:100% (fill DynamicColumn slot) + cheight:14 (fixed height)
- Model plaza root is VBox (not VScrollPanel), scroll delegated to cards area
2026-07-29 14:33:09 +08:00
a1a6a583e3 fix: buttons update RefreshWidget directly, VScrollPanel root, cwidth 22
- All filter buttons now script-update rw.opts.url + rw.show_widget()
  instead of urlwidget target navigation (which silently failed)
- Root wrapped in VScrollPanel so scroll position preserved
- Card cwidth 25→22 for better column fit
- Page buttons also use RefreshWidget update pattern
2026-07-29 13:53:23 +08:00
8a3d30c0c6 fix: pricing as string, fixed cwidth 25, pagination (20/page)
- Handle llm.pricing_display when it's a string (not array)
- Card cwidth 15→25 to fill DynamicColumn properly
- Added page/page_size pagination with prev/next buttons
- page param forwarded through model_plaza→plaza_model_cards
2026-07-29 13:37:58 +08:00
7148656f99 fix: dedupe providers, SearchBar input, urlwidget internal nav
- Removed iconid from get_llmproviders GROUP BY (caused duplicates)
- Replaced KeyinText with SearchBar (built-in input+search+clear)
- Switched navigation from window.location.assign to urlwidget
  targeting sage_main_content (stays in workspace, no full reload)
2026-07-29 13:22:53 +08:00
cb53b9aeeb fix: -@ target -> window.location.assign, Input -> KeyinText
-@ is not a valid CSS selector, Bricks parses it as '#@' and fails.
Switched all filter button binds to actiontype:script with window.location.assign().
Replaced nonexistent Input widget with KeyinText for search.
2026-07-29 12:31:40 +08:00
32000e8757 ui: plaza card layout — fixed VBox, title 2ch, filler scroll with desc+price
Each card is a cwidth:15 cheight:12 VBox:
- Title row: cheight:2 (icon + name)
- VScrollPanel css:filler: description (height:auto) + pricing (css:filler)
2026-07-29 12:00:04 +08:00
0cb307407c bugfix 2026-07-29 11:50:00 +08:00
bc23679c1b bugfix 2026-07-29 11:43:26 +08:00
78dd2b0788 fix: cate.llms -> cate['llms'] — get_llms_by_catelog_to_customer returns plain dict not DictObject 2026-07-29 11:15:36 +08:00
1c25542e72 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.
2026-07-28 17:55:17 +08:00
68dd92a353 sort: add a.name as final sort column in model listing queries
get_llmage_llm and get_llms_by_catelog had ORDER BY ending
at a.id — model names within same provider/catalog appeared
in arbitrary order. Added a.name as final sort key.
2026-07-28 16:23:41 +08:00
a2937d3cff fix: strip line number corruption from 12 media DSPY files
The ad5d8c0 commit had line number prefixes (1|, 2|, ...) baked into
file content. Also fixed cache call bugs:
- params_kw.model or 'qwen3-max' -> params_kw.model
- 'ktv_pipeline' hardcoded -> catelogid variable
- extra tab before params_kw.llmcatelogid
- missing f-string braces in debug()
2026-07-27 10:22:34 +08:00
a4aa40faf5 fix: llm上线检查定价数据显示实际内容而非记录数
改用 pricing.pricing.get_pricing_display() 获取格式化定价明细,
替换原来的 select count(*) 只显示记录条数
2026-07-24 23:44:00 +08:00
287e6c5a40 fix: 修复6个v1 DSPY缓存替换造成的缩进错误 2026-07-24 16:56:50 +08:00