Skip to content
Merged
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
1 change: 1 addition & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3852,6 +3852,7 @@ export default function App() {
const eventId = turn.meta?.eventId;
const sid = sessionId;
if (!eventId || !sid || !currentRuntime) return;
if (cloudProvider === "byteplus") return;
const output = turnText(turn);
const previousFeedback = turn.meta?.feedback;
const optimisticFeedback = {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/adk/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ export interface AgentFeedbackCasesResponse {
projectName: string;
sets: AgentFeedbackSetSummary[];
items: AgentFeedbackCase[];
unsupported?: boolean;
unsupportedMessage?: string;
}

export type AutomaticEvaluationState = "pending" | "running";
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/adk/cloudProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const VOLCENGINE_REGIONS: CloudRegionOption[] = [
];

const BYTEPLUS_REGIONS: CloudRegionOption[] = [
{ value: BYTEPLUS_DEFAULT_REGION, label: "ap-southeast-1 (Singapore)" },
{ value: BYTEPLUS_DEFAULT_REGION, label: BYTEPLUS_DEFAULT_REGION },
];

export function cloudRegionOptions(provider: CloudProvider): CloudRegionOption[] {
Expand Down
54 changes: 30 additions & 24 deletions frontend/src/create/skills/skillhub.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Volcengine Skill Hub client (the backend behind findskill.com /
// skills.volces.com). Endpoints are proxied via vite `/skillhub` to dodge the
// missing CORS headers:
// GET /v1/skills?query=<q>&namespace=public -> { Skills: [...] }
// GET /v1/skills/download/<slug>?namespace=<ns> -> application/zip
// skills.volces.com). Search uses the same normalized Studio harness endpoint
// as the in-chat skill picker. Downloads still use `/skillhub` because the
// selected zip is unpacked client-side into the generated project:
// GET /harness/skills/findskill?query=<q> -> { items: [...] }
// GET /skillhub/v1/skills/download/<slug>?namespace=<ns> -> application/zip
//
// Skills are downloaded as a zip and unpacked client-side into project files.

Expand All @@ -15,17 +16,16 @@ import {
import type { SkillHit, SelectedSkill } from "./types";
import { unzip } from "./zip";

const BASE = "/skillhub/v1/skills";
const DOWNLOAD_BASE = "/skillhub/v1/skills";
const SEARCH_BASE = "/harness/skills/findskill";

interface RawSkill {
Id?: string;
Slug?: string;
Name?: string;
Description?: string;
Namespace?: string;
SourceRepo?: string;
DownloadCount?: number;
Metadata?: { DisplayDescription?: string };
slug?: string;
name?: string;
description?: string;
sourceRepo?: string;
downloadCount?: number;
version?: string;
}

/** Search the public Skill Hub. */
Expand All @@ -34,22 +34,28 @@ export async function searchSkills(
namespace = "public",
): Promise<SkillHit[]> {
const q = query.trim();
const url = `${BASE}?query=${encodeURIComponent(q)}&namespace=${encodeURIComponent(namespace)}`;
const params = new URLSearchParams({
query: q,
page_number: "1",
page_size: "20",
});
const url = `${SEARCH_BASE}?${params.toString()}`;
const res = await fetch(url, {
headers: { accept: "application/json" },
signal: requestSignal(undefined, DEFAULT_REQUEST_TIMEOUT_MS),
});
if (!res.ok) throw new Error(`搜索失败 (${res.status})`);
const data = (await res.json()) as { Skills?: RawSkill[] };
return (data.Skills ?? []).map((s) => ({
const data = (await res.json()) as { items?: RawSkill[] };
return (data.items ?? []).map((s) => ({
source: "skillhub" as const,
id: s.Id ?? s.Slug ?? "",
slug: s.Slug ?? "",
name: s.Name ?? s.Slug ?? "",
description: s.Metadata?.DisplayDescription || s.Description || "",
namespace: s.Namespace ?? namespace,
sourceRepo: s.SourceRepo,
downloadCount: s.DownloadCount,
id: s.slug ?? s.name ?? "",
slug: s.slug ?? "",
name: s.name ?? s.slug ?? "",
description: s.description ?? "",
namespace,
sourceRepo: s.sourceRepo,
downloadCount: s.downloadCount,
version: s.version,
}));
}

Expand All @@ -60,7 +66,7 @@ export async function downloadSkillHubSkill(
): Promise<ProjectFile[]> {
const slug = s.slug || "";
const namespace = s.namespace || "public";
const url = `${BASE}/download/${slug}?namespace=${encodeURIComponent(namespace)}`;
const url = `${DOWNLOAD_BASE}/download/${slug}?namespace=${encodeURIComponent(namespace)}`;
const res = await fetch(url, {
signal: requestSignal(undefined, TRANSFER_REQUEST_TIMEOUT_MS),
});
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/ui/AgentWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@ export function AgentWorkspace({
const [feedbackSets, setFeedbackSets] = useState<AgentFeedbackSetSummary[]>([]);
const [feedbackCasesLoading, setFeedbackCasesLoading] = useState(false);
const [feedbackCasesError, setFeedbackCasesError] = useState("");
const [feedbackCasesUnsupported, setFeedbackCasesUnsupported] = useState("");
const [feedbackReloadToken, setFeedbackReloadToken] = useState(0);
const [optimizationGroups, setOptimizationGroups] = useState<OptimizationGroup[]>([]);
const [optimizationsLoading, setOptimizationsLoading] = useState(false);
Expand Down Expand Up @@ -1623,6 +1624,7 @@ export function AgentWorkspace({
setFeedbackCases(cached ? feedbackCasesFromResponse(cached) : []);
setFeedbackSets(cached?.sets ?? []);
setFeedbackCasesError("");
setFeedbackCasesUnsupported(cached?.unsupportedMessage ?? "");
if (section !== "evaluations" || !runtimeId) {
setFeedbackCasesLoading(false);
return;
Expand All @@ -1642,10 +1644,12 @@ export function AgentWorkspace({
if (cancelled) return;
setFeedbackSets(response.sets);
setFeedbackCases(feedbackCasesFromResponse(response));
setFeedbackCasesUnsupported(response.unsupportedMessage ?? "");
})
.catch((cause) => {
if (!cancelled) {
setFeedbackCasesError(cause instanceof Error ? cause.message : String(cause));
setFeedbackCasesUnsupported("");
}
})
.finally(() => {
Expand Down Expand Up @@ -2927,6 +2931,7 @@ export function AgentWorkspace({
cases={visibleCases}
loading={feedbackCasesLoading && visibleCases.length === 0}
error={feedbackCasesError}
notice={feedbackCasesUnsupported}
runtimeBacked={Boolean(selectedAgent?.runtimeId)}
selectionMode={caseSelectionMode}
selectedCaseIds={selectedCaseIds}
Expand Down Expand Up @@ -3132,6 +3137,7 @@ function CaseTable({
cases,
loading = false,
error = "",
notice = "",
runtimeBacked = false,
selectionMode = false,
selectedCaseIds,
Expand All @@ -3148,6 +3154,7 @@ function CaseTable({
cases: AgentCase[];
loading?: boolean;
error?: string;
notice?: string;
runtimeBacked?: boolean;
selectionMode?: boolean;
selectedCaseIds?: Set<string>;
Expand Down Expand Up @@ -3177,6 +3184,8 @@ function CaseTable({
<span>{error}</span>
{onRetry && <button type="button" onClick={onRetry}>重试</button>}
</div>
) : notice ? (
<div className="aw-case-empty">{notice}</div>
) : cases.length === 0 ? (
<div className="aw-case-empty">
{runtimeBacked ? "暂无用户反馈案例" : "没有匹配的案例"}
Expand Down
49 changes: 27 additions & 22 deletions frontend/src/ui/ProjectPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,9 @@ export function ProjectPreview({
inMemorySession ? "1" : "5",
);
const [createEvaluationSets, setCreateEvaluationSets] = useState(true);
const supportsEvaluationSets = cloudProvider !== "byteplus";
const effectiveCreateEvaluationSets =
supportsEvaluationSets && createEvaluationSets;
const [deploymentActionTarget, setDeploymentActionTarget] =
useState<HTMLElement | null>(null);
const mountedRef = useRef(true);
Expand All @@ -901,7 +904,7 @@ export function ProjectPreview({
const deploymentStepsWithInstanceUpdate = needsInstanceUpdate
? [...baseDeploymentSteps, INSTANCE_UPDATE_STEP]
: baseDeploymentSteps;
const deploymentSteps = createEvaluationSets
const deploymentSteps = effectiveCreateEvaluationSets
? [...deploymentStepsWithInstanceUpdate, EVALUATION_SET_STEP]
: deploymentStepsWithInstanceUpdate;

Expand Down Expand Up @@ -1233,7 +1236,7 @@ export function ProjectPreview({
instanceRange: needsInstanceUpdate
? { min: instanceRange.min, max: instanceRange.max }
: undefined,
createEvaluationSets,
createEvaluationSets: effectiveCreateEvaluationSets,
};
onDeploymentTaskChange?.(initialTask);
onDeploymentStarted?.(initialTask);
Expand Down Expand Up @@ -1325,7 +1328,7 @@ export function ProjectPreview({
: { type: "api_key" as const },
}
: {}),
createEvaluationSets,
createEvaluationSets: effectiveCreateEvaluationSets,
...(feishuEnabled
? {
im: {
Expand Down Expand Up @@ -2074,25 +2077,27 @@ export function ProjectPreview({
</div>
</section>

<section className="pp-config-section">
<div className="pp-config-label">评测集</div>
<label className="pp-evaluation-set-option">
<input
type="checkbox"
checked={createEvaluationSets}
disabled={deploying}
onChange={(event) =>
setCreateEvaluationSets(event.currentTarget.checked)
}
/>
<span>
<strong>自动创建评测集</strong>
<small>
部署成功后,自动创建 Good Case 和 Bad Case 评测集。
</small>
</span>
</label>
</section>
{supportsEvaluationSets && (
<section className="pp-config-section">
<div className="pp-config-label">评测集</div>
<label className="pp-evaluation-set-option">
<input
type="checkbox"
checked={createEvaluationSets}
disabled={deploying}
onChange={(event) =>
setCreateEvaluationSets(event.currentTarget.checked)
}
/>
<span>
<strong>自动创建评测集</strong>
<small>
部署成功后,自动创建 Good Case 和 Bad Case 评测集。
</small>
</span>
</label>
</section>
)}

<section className="pp-config-section pp-env-section">
<div className="pp-env-head">
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/ui/StudioUpdateControl.css
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@
grid-template-columns: 30px minmax(0, 1fr);
column-gap: 10px;
width: min(500px, calc(100vw - 32px));
max-width: calc(100vw - 32px);
min-width: 0;
overflow: hidden;
}

.studio-update-dialog > .studio-update-dialog-mark {
Expand All @@ -111,6 +113,8 @@

.studio-update-dialog > :not(.studio-update-dialog-mark, .confirm-title) {
grid-column: 1 / -1;
width: 100%;
max-width: 100%;
min-width: 0;
}

Expand All @@ -121,7 +125,10 @@
.studio-update-dialog .studio-update-progress small,
.studio-update-dialog .studio-update-progress-note,
.studio-update-dialog .studio-update-console-link {
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}

.studio-update-field {
Expand Down
14 changes: 11 additions & 3 deletions frontend/tests/deploymentConfigUi.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -413,13 +413,21 @@ test("creates feedback evaluation sets by default and sends the deployment choic
projectPreviewSource,
/useState\(true\)[\s\S]*?<strong>自动创建评测集<\/strong>[\s\S]*?部署成功后,自动创建 Good Case 和 Bad Case 评测集。/,
);
assert.match(
projectPreviewSource,
/const supportsEvaluationSets = cloudProvider !== "byteplus"/,
);
assert.match(
projectPreviewSource,
/const effectiveCreateEvaluationSets =[\s\S]*supportsEvaluationSets && createEvaluationSets/,
);
assert.match(
projectPreviewSource,
/type="checkbox"[\s\S]*?checked=\{createEvaluationSets\}[\s\S]*?setCreateEvaluationSets/,
);
assert.match(
projectPreviewSource,
/createEvaluationSets,\s*[\s\S]*?envs,/,
/createEvaluationSets: effectiveCreateEvaluationSets,\s*[\s\S]*?envs,/,
);
assert.match(
adkClientSource,
Expand All @@ -431,10 +439,10 @@ test("creates feedback evaluation sets by default and sends the deployment choic
);
assert.match(
projectPreviewSource,
/createEvaluationSets[\s\S]*?\[\.\.\.deploymentStepsWithInstanceUpdate, EVALUATION_SET_STEP\][\s\S]*?: deploymentStepsWithInstanceUpdate/,
/effectiveCreateEvaluationSets[\s\S]*?\[\.\.\.deploymentStepsWithInstanceUpdate, EVALUATION_SET_STEP\][\s\S]*?: deploymentStepsWithInstanceUpdate/,
);
assert.match(
projectPreviewSource,
/const initialTask: DeploymentTaskUpdate =[\s\S]*?createEvaluationSets,\s*\n\s*};/,
/const initialTask: DeploymentTaskUpdate =[\s\S]*?createEvaluationSets: effectiveCreateEvaluationSets,\s*\n\s*};/,
);
});
6 changes: 6 additions & 0 deletions frontend/tests/markdownPromptEditor.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const skillHubPickerSource = readFileSync(
new URL("../src/create/SkillHubPicker.tsx", import.meta.url),
"utf8",
);
const skillHubSource = readFileSync(
new URL("../src/create/skills/skillhub.ts", import.meta.url),
"utf8",
);
const skillSpacePickerSource = readFileSync(
new URL("../src/create/SkillSpacePicker.tsx", import.meta.url),
"utf8",
Expand Down Expand Up @@ -669,6 +673,8 @@ test("skill sources open in a fixed-height dialog above a six-row selected list"
assert.match(createSource, /label: "AgentKit Skills 中心"/);
assert.doesNotMatch(createSource, /label: "SkillSpace"/);
assert.match(createSource, /label: "火山 Find Skill 技能广场"/);
assert.match(skillHubSource, /const SEARCH_BASE = "\/harness\/skills\/findskill"/);
assert.match(skillHubSource, /const DOWNLOAD_BASE = "\/skillhub\/v1\/skills"/);
assert.match(createSource, /function AgentKitSkillsIcon/);
assert.match(
createSource,
Expand Down
17 changes: 17 additions & 0 deletions frontend/tests/messageFeedback.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ test("feedback selection updates immediately and uses a neutral solid icon", ()
assert.doesNotMatch(stylesSource, /\.feedback-btn--bad[\s\S]{0,120}destructive/);
});

test("BytePlus feedback buttons are visible but do not submit evaluation feedback", () => {
const handler = appSource.slice(
appSource.indexOf("const rateAssistantTurn"),
appSource.indexOf("const send = async"),
);
assert.ok(
handler.indexOf('if (cloudProvider === "byteplus") return;') <
handler.indexOf('syncStatus: "syncing"'),
);
assert.ok(
handler.indexOf('if (cloudProvider === "byteplus") return;') <
handler.indexOf("await submitMessageFeedback"),
);
assert.match(appSource, /aria-label="赞"/);
assert.match(appSource, /aria-label="踩"/);
});

test("chat feedback row has no evaluation case shortcut", () => {
assert.doesNotMatch(appSource, /const openCurrentAgentCases/);
assert.match(appSource, /feedbackCasePreview=\{feedbackCasePreview\}/);
Expand Down
Loading
Loading