mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 20:14:16 +00:00
fix batch 1
This commit is contained in:
1 parent
7530e54aec
commit
49a3a2a220
6 files changed
+87
-7
No files matched your search
@@ -43,7 +43,7 @@ class AgentsRepository:
|
||||
"limited_token_mode", "limited_request_mode",
|
||||
"allow_system_prompt_override",
|
||||
"shared", "shared_token", "shared_metadata",
|
||||
"tools", "json_schema", "models", "config", "legacy_mongo_id",
|
||||
"tools", "json_schema", "models", "config", "resource_sponsors", "legacy_mongo_id",
|
||||
"created_at", "updated_at", "last_used_at",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { act } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import type { Agent, ResourceSponsor } from '../types';
|
||||
import SponsoredResourcesNotice from './SponsoredResourcesNotice';
|
||||
|
||||
@@ -86,6 +89,36 @@ describe('SponsoredResourcesNotice', () => {
|
||||
expect(container.innerHTML).toBe('');
|
||||
});
|
||||
|
||||
it('joins names for the app language and does not escape them', async () => {
|
||||
await i18n.use(initReactI18next).init({
|
||||
lng: 'jp',
|
||||
resources: {
|
||||
jp: {
|
||||
translation: {
|
||||
agents: {
|
||||
form: { sponsors: { addedBy: '{{person}}: {{names}}' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
try {
|
||||
await render({
|
||||
...baseAgent,
|
||||
resource_sponsors: [
|
||||
sponsor({ id: 'docs/a' }),
|
||||
sponsor({ id: 'docs/b' }),
|
||||
],
|
||||
});
|
||||
const expected = new Intl.ListFormat('ja', {
|
||||
type: 'conjunction',
|
||||
}).format(['name-docs/a', 'name-docs/b']);
|
||||
expect(container.textContent).toBe(`bob@example.com: ${expected}`);
|
||||
} finally {
|
||||
await i18n.changeLanguage('en');
|
||||
}
|
||||
});
|
||||
|
||||
it('splits running and no-longer-running items', async () => {
|
||||
await render({
|
||||
...baseAgent,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
||||
import { can, isOwner } from '../../utils/accessUtils';
|
||||
import { intlLocale } from '../../utils/dateTimeUtils';
|
||||
import type { Agent, ResourceSponsor } from '../types';
|
||||
|
||||
type SponsoredResourcesNoticeProps = {
|
||||
@@ -34,7 +35,7 @@ export default function SponsoredResourcesNotice({
|
||||
agent,
|
||||
resolveName,
|
||||
}: SponsoredResourcesNoticeProps) {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const sponsors = agent.resource_sponsors ?? [];
|
||||
const showAttachNote = !isOwner(agent) && can(agent, 'edit');
|
||||
const active = groupByPerson(sponsors.filter((s) => s.active));
|
||||
@@ -42,8 +43,11 @@ export default function SponsoredResourcesNotice({
|
||||
|
||||
if (!showAttachNote && sponsors.length === 0) return null;
|
||||
|
||||
const listFormat = new Intl.ListFormat(intlLocale(i18n.language), {
|
||||
type: 'conjunction',
|
||||
});
|
||||
const names = (items: ResourceSponsor[]) =>
|
||||
items.map((item) => resolveName(item)).join(', ');
|
||||
listFormat.format(items.map((item) => resolveName(item)));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 sm:col-span-2">
|
||||
@@ -63,6 +67,7 @@ export default function SponsoredResourcesNotice({
|
||||
{active.map(([person, items]) => (
|
||||
<p key={person}>
|
||||
{t('agents.form.sponsors.addedBy', {
|
||||
interpolation: { escapeValue: false },
|
||||
person,
|
||||
names: names(items),
|
||||
})}
|
||||
@@ -78,6 +83,7 @@ export default function SponsoredResourcesNotice({
|
||||
{inactive.map(([person, items]) => (
|
||||
<p key={person}>
|
||||
{t('agents.form.sponsors.unavailable', {
|
||||
interpolation: { escapeValue: false },
|
||||
person,
|
||||
names: names(items),
|
||||
})}
|
||||
|
||||
@@ -157,8 +157,15 @@ export function capabilityLines(
|
||||
}
|
||||
|
||||
// A teams API error carries the server's message; anything else (a network
|
||||
// failure) falls back to the caller's generic copy.
|
||||
export const errorMessage = (error: unknown, fallback: string): string =>
|
||||
error instanceof Error && error.name === 'TeamsApiError' && error.message
|
||||
? error.message
|
||||
// failure) falls back to the caller's generic copy. Matched by shape, not
|
||||
// `instanceof`: a thunk's unwrap() rethrows a serialized plain object.
|
||||
export const errorMessage = (error: unknown, fallback: string): string => {
|
||||
const e = error as { name?: unknown; message?: unknown } | null;
|
||||
return typeof e === 'object' &&
|
||||
e !== null &&
|
||||
e.name === 'TeamsApiError' &&
|
||||
typeof e.message === 'string' &&
|
||||
e.message
|
||||
? e.message
|
||||
: fallback;
|
||||
};
|
||||
@@ -10,6 +10,7 @@ vi.mock('../api/services/teamsService', () => ({
|
||||
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import { errorMessage } from './accessSettings';
|
||||
import reducer, { deleteTeam, loadTeams, Team } from './teamsSlice';
|
||||
|
||||
const team: Team = { id: 't1', name: 'Ops', slug: 'ops', owner_id: 'u1' };
|
||||
@@ -41,6 +42,33 @@ describe('teamsSlice', () => {
|
||||
expect(store.getState().teams.teams).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("surfaces the server's reason through unwrap()", async () => {
|
||||
// unwrap() rethrows a serialized plain object, not the Error instance.
|
||||
remove.mockRejectedValue(
|
||||
Object.assign(new Error('Only the owner can delete'), {
|
||||
name: 'TeamsApiError',
|
||||
}),
|
||||
);
|
||||
const store = makeStore();
|
||||
const rejection = await store
|
||||
.dispatch(deleteTeam({ id: 't1', token: null }))
|
||||
.unwrap()
|
||||
.catch((error: unknown) => error);
|
||||
expect(errorMessage(rejection, 'fallback')).toBe(
|
||||
'Only the owner can delete',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the fallback for a non-teams rejection', async () => {
|
||||
remove.mockRejectedValue(new Error('network down'));
|
||||
const store = makeStore();
|
||||
const rejection = await store
|
||||
.dispatch(deleteTeam({ id: 't1', token: null }))
|
||||
.unwrap()
|
||||
.catch((error: unknown) => error);
|
||||
expect(errorMessage(rejection, 'fallback')).toBe('fallback');
|
||||
});
|
||||
|
||||
it('keeps the team when a 2xx body says success:false', async () => {
|
||||
remove.mockResolvedValue({ success: false, message: 'nope' });
|
||||
const store = makeStore();
|
||||
|
||||
@@ -49,6 +49,12 @@ class TestCreate:
|
||||
)
|
||||
assert doc["legacy_mongo_id"] == "507f1f77bcf86cd799439011"
|
||||
|
||||
def test_create_with_resource_sponsors(self, pg_conn):
|
||||
repo = _repo(pg_conn)
|
||||
sponsors = {"tool:t1": "editor-1"}
|
||||
doc = repo.create("u", "a", "draft", resource_sponsors=sponsors)
|
||||
assert doc["resource_sponsors"] == sponsors
|
||||
|
||||
def test_create_normalizes_blank_key_to_null(self, pg_conn):
|
||||
repo = _repo(pg_conn)
|
||||
doc = repo.create("u", "a", "draft", key="")
|
||||
|
||||
Reference in new issue
Block a user