fix: playground model id alignment across org boundaries
Two QA-reported bugs on the "Open in Playground" path. Both come
down to the model id the playground submits not matching what
``/v1/models`` / the dispatcher key off:
* Routes page (OSS UI) emitted ``default/<name>`` for routes in the
platform Org and 404'd. ``use-open-playground`` keyed off the
``is_platform`` flag alone; stale caches that drop the flag
slipped through. Also accept the well-known ``name === 'default'``
(``PLATFORM_PRINCIPAL_NAME`` on the backend) as a fallback signal.
* My Models page (enterprise UI, non-admin) emitted bare ``<name>``
with no Org prefix for non-platform routes. Use ``model.name``
from ``/v2/my-models`` verbatim — the backend ("fix: principal
prefix in my-models") now rewrites that field to the OpenAI-style
id server-side, which also closes the cross-Org grant gap a
client-side cache lookup can't (the granting Org isn't in the
caller's member list). Card title now shows the prefixed id, so
users can tell apart same-named models from different Orgs.
Routes page keeps ``useOpenPlayground`` — that surface is always
scoped to the caller's own Org, the local cache is sufficient, and
``/model-routes`` still returns the raw ``name``.
Also drops the now-unused ``onClick`` prop on ``ModelItem``: the
card had ``clickable={false}`` so the parent-passed handler was
already dead code; the Button drives the playground navigation.
This commit is contained in:
@@ -119,13 +119,16 @@ const renderTag = (item: any, index = 0) => {
|
|||||||
|
|
||||||
const ModelItem: React.FC<{
|
const ModelItem: React.FC<{
|
||||||
model: Record<string, any>;
|
model: Record<string, any>;
|
||||||
onClick: (model: any) => void;
|
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { model, onClick } = props;
|
const { model } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleOpenPlayGround = () => {
|
// ``model.name`` from ``/v2/my-models`` is the OpenAI-style id
|
||||||
|
// (org-prefixed for non-platform routes, bare for platform). Use it
|
||||||
|
// verbatim — the playground / dispatcher both key off that exact id.
|
||||||
|
const handleOpenPlayGroundClick = () => {
|
||||||
|
const modelName = encodeURIComponent(model.name);
|
||||||
for (const [category, path] of Object.entries(categoryToPathMap)) {
|
for (const [category, path] of Object.entries(categoryToPathMap)) {
|
||||||
if (
|
if (
|
||||||
model.categories?.includes(category) &&
|
model.categories?.includes(category) &&
|
||||||
@@ -134,15 +137,15 @@ const ModelItem: React.FC<{
|
|||||||
modelCategoriesMap.speech_to_text
|
modelCategoriesMap.speech_to_text
|
||||||
].includes(category)
|
].includes(category)
|
||||||
) {
|
) {
|
||||||
navigate(`${path}&model=${model.name}`);
|
navigate(`${path}&model=${modelName}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (model.categories?.includes(category)) {
|
if (model.categories?.includes(category)) {
|
||||||
navigate(`${path}?model=${model.name}`);
|
navigate(`${path}?model=${modelName}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
navigate(`/playground/chat?model=${model.name}`);
|
navigate(`/playground/chat?model=${modelName}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// context length
|
// context length
|
||||||
@@ -167,7 +170,6 @@ const ModelItem: React.FC<{
|
|||||||
<CardWrapper>
|
<CardWrapper>
|
||||||
<TemplateCard
|
<TemplateCard
|
||||||
height={140}
|
height={140}
|
||||||
onClick={() => onClick(model)}
|
|
||||||
clickable={false}
|
clickable={false}
|
||||||
hoverable={true}
|
hoverable={true}
|
||||||
ghost
|
ghost
|
||||||
@@ -237,7 +239,7 @@ const ModelItem: React.FC<{
|
|||||||
size="middle"
|
size="middle"
|
||||||
className="btn"
|
className="btn"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleOpenPlayGround}
|
onClick={handleOpenPlayGroundClick}
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: 'models.openinplayground' })}
|
{intl.formatMessage({ id: 'models.openinplayground' })}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -8,19 +8,14 @@ import {
|
|||||||
PageTools,
|
PageTools,
|
||||||
TemplateCardList
|
TemplateCardList
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl, useNavigate } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||||
import { Button, Input, Space } from 'antd';
|
import { Button, Input, Space } from 'antd';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import PageBox from '../_components/page-box';
|
import PageBox from '../_components/page-box';
|
||||||
import { MY_MODELS_API, queryMyModels } from './apis';
|
import { MY_MODELS_API, queryMyModels } from './apis';
|
||||||
import ModelItem from './components/model-item';
|
import ModelItem from './components/model-item';
|
||||||
import {
|
import { categoryOptions, MyModelsStatusValueMap } from './config';
|
||||||
categoryOptions,
|
|
||||||
modelCategoriesMap,
|
|
||||||
MyModelsStatusValueMap
|
|
||||||
} from './config';
|
|
||||||
import { categoryToPathMap } from './config/button-actions';
|
|
||||||
const Dot = ({ color }: { color: string }) => {
|
const Dot = ({ color }: { color: string }) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -45,7 +40,6 @@ const optionRender = (item: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UserModels: React.FC = () => {
|
const UserModels: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const {
|
const {
|
||||||
dataSource,
|
dataSource,
|
||||||
queryParams,
|
queryParams,
|
||||||
@@ -96,28 +90,8 @@ const UserModels: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnClick = (model: any) => {
|
|
||||||
for (const [category, path] of Object.entries(categoryToPathMap)) {
|
|
||||||
if (
|
|
||||||
model.categories?.includes(category) &&
|
|
||||||
[
|
|
||||||
modelCategoriesMap.text_to_speech,
|
|
||||||
modelCategoriesMap.speech_to_text
|
|
||||||
].includes(category)
|
|
||||||
) {
|
|
||||||
navigate(`${path}&model=${model.name}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (model.categories?.includes(category)) {
|
|
||||||
navigate(`${path}?model=${model.name}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
navigate(`/playground/chat?model=${model.name}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderCard = (data: any) => {
|
const renderCard = (data: any) => {
|
||||||
return <ModelItem model={data} onClick={handleOnClick} />;
|
return <ModelItem model={data} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMore = useMemoizedFn((nextPage: number) => {
|
const loadMore = useMemoizedFn((nextPage: number) => {
|
||||||
|
|||||||
@@ -8,8 +8,15 @@ const useOpenPlayground = () => {
|
|||||||
|
|
||||||
const generateModelName = (row: any) => {
|
const generateModelName = (row: any) => {
|
||||||
const org = getOrgById(row.owner_principal_id) ?? getCurrentOrg();
|
const org = getOrgById(row.owner_principal_id) ?? getCurrentOrg();
|
||||||
|
// The platform Org is always named ``default`` (backend constant
|
||||||
|
// ``PLATFORM_PRINCIPAL_NAME``); its models are reported by
|
||||||
|
// ``/v1/models`` without a prefix. Match by name too, not just the
|
||||||
|
// ``is_platform`` flag — pre-multi-tenancy OSS caches and any other
|
||||||
|
// path that drops the flag would otherwise emit ``default/<name>``
|
||||||
|
// and 404 against the unprefixed model id.
|
||||||
|
const isPlatformOrg = org?.is_platform || org?.name === 'default';
|
||||||
const rawModel =
|
const rawModel =
|
||||||
org?.name && !org.is_platform ? `${org.name}/${row.name}` : row.name;
|
org?.name && !isPlatformOrg ? `${org.name}/${row.name}` : row.name;
|
||||||
return rawModel;
|
return rawModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user