Add fastgpt as seperate assistant mode
This commit is contained in:
@@ -263,6 +263,7 @@ export const AssistantsPage: React.FC = () => {
|
||||
botCannotBeInterrupted: false,
|
||||
interruptionSensitivity: 180,
|
||||
configMode: 'platform',
|
||||
appId: '',
|
||||
};
|
||||
try {
|
||||
const created = await createAssistant(newAssistantPayload);
|
||||
@@ -874,6 +875,20 @@ export const AssistantsPage: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedAssistant.configMode === 'fastgpt' && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<Key className="w-4 h-4 mr-2 text-primary" /> 搴旂敤 ID (APP ID)
|
||||
</label>
|
||||
<Input
|
||||
value={selectedAssistant.appId || ''}
|
||||
onChange={(e) => updateAssistant('appId', e.target.value)}
|
||||
placeholder="璇疯緭鍏?FastGPT App ID..."
|
||||
className="bg-white/5 border-white/10 focus:border-primary/50 font-mono text-xs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<Terminal className="w-4 h-4 mr-2 text-primary" /> 密钥 (API KEY)
|
||||
@@ -2226,6 +2241,23 @@ type DebugChoicePromptOption = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
type DebugFastGPTInteractiveOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
type DebugFastGPTInteractiveField = {
|
||||
name: string;
|
||||
label: string;
|
||||
inputType: string;
|
||||
required: boolean;
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
options: DebugFastGPTInteractiveOption[];
|
||||
};
|
||||
|
||||
type DebugTextPromptDialogState = {
|
||||
open: boolean;
|
||||
message: string;
|
||||
@@ -2243,9 +2275,31 @@ type DebugChoicePromptDialogState = {
|
||||
voiceText?: string;
|
||||
};
|
||||
|
||||
type DebugFastGPTInteractiveDialogState = {
|
||||
open: boolean;
|
||||
interactionType: 'userSelect' | 'userInput';
|
||||
title: string;
|
||||
description: string;
|
||||
prompt: string;
|
||||
options: DebugFastGPTInteractiveOption[];
|
||||
form: DebugFastGPTInteractiveField[];
|
||||
multiple: boolean;
|
||||
required: boolean;
|
||||
selectedValues: string[];
|
||||
fieldValues: Record<string, string>;
|
||||
pendingResult?: DebugPromptPendingResult;
|
||||
submitLabel: string;
|
||||
cancelLabel: string;
|
||||
};
|
||||
|
||||
type DebugPromptQueueItem =
|
||||
| { kind: 'text'; payload: Omit<DebugTextPromptDialogState, 'open'> }
|
||||
| { kind: 'choice'; payload: Omit<DebugChoicePromptDialogState, 'open'> };
|
||||
| { kind: 'choice'; payload: Omit<DebugChoicePromptDialogState, 'open'> }
|
||||
| {
|
||||
kind: 'fastgpt';
|
||||
payload: Omit<DebugFastGPTInteractiveDialogState, 'open' | 'selectedValues' | 'fieldValues'>
|
||||
& Partial<Pick<DebugFastGPTInteractiveDialogState, 'selectedValues' | 'fieldValues'>>;
|
||||
};
|
||||
|
||||
const normalizeChoicePromptOptions = (rawOptions: unknown[]): DebugChoicePromptOption[] => {
|
||||
const usedIds = new Set<string>();
|
||||
@@ -2277,6 +2331,74 @@ const normalizeChoicePromptOptions = (rawOptions: unknown[]): DebugChoicePromptO
|
||||
return resolved;
|
||||
};
|
||||
|
||||
const normalizeFastGPTInteractiveOptions = (rawOptions: unknown[]): DebugFastGPTInteractiveOption[] => {
|
||||
const usedIds = new Set<string>();
|
||||
const resolved: DebugFastGPTInteractiveOption[] = [];
|
||||
rawOptions.forEach((rawOption, index) => {
|
||||
let id = `option_${index + 1}`;
|
||||
let label = '';
|
||||
let value = '';
|
||||
let description = '';
|
||||
if (typeof rawOption === 'string' || typeof rawOption === 'number' || typeof rawOption === 'boolean') {
|
||||
label = String(rawOption).trim();
|
||||
value = label;
|
||||
} else if (rawOption && typeof rawOption === 'object') {
|
||||
const row = rawOption as Record<string, unknown>;
|
||||
label = String(row.label ?? row.text ?? row.name ?? row.value ?? '').trim();
|
||||
value = String(row.value ?? row.label ?? row.name ?? row.id ?? '').trim();
|
||||
id = String(row.id ?? value ?? id).trim() || id;
|
||||
description = String(
|
||||
row.description
|
||||
?? row.desc
|
||||
?? row.intro
|
||||
?? row.summary
|
||||
?? row.remark
|
||||
?? ''
|
||||
).trim();
|
||||
}
|
||||
if (!label && !value) return;
|
||||
if (!label) label = value;
|
||||
if (!value) value = label;
|
||||
if (usedIds.has(id)) {
|
||||
let suffix = 2;
|
||||
while (usedIds.has(`${id}_${suffix}`)) suffix += 1;
|
||||
id = `${id}_${suffix}`;
|
||||
}
|
||||
usedIds.add(id);
|
||||
resolved.push({ id, label, value, description });
|
||||
});
|
||||
return resolved;
|
||||
};
|
||||
|
||||
const normalizeFastGPTInteractiveFields = (rawForm: unknown[]): DebugFastGPTInteractiveField[] => {
|
||||
const usedNames = new Set<string>();
|
||||
const resolved: DebugFastGPTInteractiveField[] = [];
|
||||
rawForm.forEach((rawField, index) => {
|
||||
if (!rawField || typeof rawField !== 'object') return;
|
||||
const row = rawField as Record<string, unknown>;
|
||||
let name = String(row.name ?? row.key ?? row.id ?? row.label ?? `field_${index + 1}`).trim() || `field_${index + 1}`;
|
||||
if (usedNames.has(name)) {
|
||||
let suffix = 2;
|
||||
while (usedNames.has(`${name}_${suffix}`)) suffix += 1;
|
||||
name = `${name}_${suffix}`;
|
||||
}
|
||||
usedNames.add(name);
|
||||
resolved.push({
|
||||
name,
|
||||
label: String(row.label ?? row.name ?? name).trim() || name,
|
||||
inputType: String(row.input_type ?? row.inputType ?? row.type ?? 'text').trim() || 'text',
|
||||
required: Boolean(row.required),
|
||||
placeholder: String(row.placeholder ?? row.description ?? row.desc ?? '').trim() || undefined,
|
||||
defaultValue:
|
||||
row.default === undefined || row.default === null
|
||||
? (row.defaultValue === undefined || row.defaultValue === null ? undefined : String(row.defaultValue))
|
||||
: String(row.default),
|
||||
options: normalizeFastGPTInteractiveOptions(Array.isArray(row.options) ? row.options : []),
|
||||
});
|
||||
});
|
||||
return resolved;
|
||||
};
|
||||
|
||||
// Stable transcription log so the scroll container is not recreated on every render (avoids scroll jumping)
|
||||
const TranscriptionLog: React.FC<{
|
||||
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
@@ -2372,8 +2494,24 @@ export const DebugDrawer: React.FC<{
|
||||
promptType: 'text',
|
||||
});
|
||||
const [choicePromptDialog, setChoicePromptDialog] = useState<DebugChoicePromptDialogState>({ open: false, question: '', options: [] });
|
||||
const [fastgptInteractiveDialog, setFastgptInteractiveDialog] = useState<DebugFastGPTInteractiveDialogState>({
|
||||
open: false,
|
||||
interactionType: 'userSelect',
|
||||
title: '',
|
||||
description: '',
|
||||
prompt: '',
|
||||
options: [],
|
||||
form: [],
|
||||
multiple: false,
|
||||
required: true,
|
||||
selectedValues: [],
|
||||
fieldValues: {},
|
||||
submitLabel: 'Continue',
|
||||
cancelLabel: 'Cancel',
|
||||
});
|
||||
const textPromptDialogRef = useRef(textPromptDialog);
|
||||
const choicePromptDialogRef = useRef(choicePromptDialog);
|
||||
const fastgptInteractiveDialogRef = useRef(fastgptInteractiveDialog);
|
||||
const promptDialogQueueRef = useRef<DebugPromptQueueItem[]>([]);
|
||||
const promptAudioRef = useRef<HTMLAudioElement | null>(null);
|
||||
const [textSessionStarted, setTextSessionStarted] = useState(false);
|
||||
@@ -2558,6 +2696,9 @@ export const DebugDrawer: React.FC<{
|
||||
if (choicePromptDialogRef.current.open) {
|
||||
closeChoicePromptDialog('dismiss', undefined, { force: true, skipQueueAdvance: true });
|
||||
}
|
||||
if (fastgptInteractiveDialogRef.current.open) {
|
||||
closeFastGPTInteractiveDialog('cancel', { force: true, skipQueueAdvance: true });
|
||||
}
|
||||
stopVoiceCapture();
|
||||
stopMedia();
|
||||
closeWs();
|
||||
@@ -2565,6 +2706,21 @@ export const DebugDrawer: React.FC<{
|
||||
promptDialogQueueRef.current = [];
|
||||
setTextPromptDialog({ open: false, message: '', promptType: 'text' });
|
||||
setChoicePromptDialog({ open: false, question: '', options: [] });
|
||||
setFastgptInteractiveDialog({
|
||||
open: false,
|
||||
interactionType: 'userSelect',
|
||||
title: '',
|
||||
description: '',
|
||||
prompt: '',
|
||||
options: [],
|
||||
form: [],
|
||||
multiple: false,
|
||||
required: true,
|
||||
selectedValues: [],
|
||||
fieldValues: {},
|
||||
submitLabel: 'Continue',
|
||||
cancelLabel: 'Cancel',
|
||||
});
|
||||
if (audioCtxRef.current) {
|
||||
void audioCtxRef.current.close();
|
||||
audioCtxRef.current = null;
|
||||
@@ -2592,6 +2748,10 @@ export const DebugDrawer: React.FC<{
|
||||
choicePromptDialogRef.current = choicePromptDialog;
|
||||
}, [choicePromptDialog]);
|
||||
|
||||
useEffect(() => {
|
||||
fastgptInteractiveDialogRef.current = fastgptInteractiveDialog;
|
||||
}, [fastgptInteractiveDialog]);
|
||||
|
||||
useEffect(() => {
|
||||
dynamicVariableSeqRef.current = 0;
|
||||
setDynamicVariables([]);
|
||||
@@ -2865,7 +3025,34 @@ export const DebugDrawer: React.FC<{
|
||||
}
|
||||
};
|
||||
|
||||
const hasActivePromptDialog = () => textPromptDialogRef.current.open || choicePromptDialogRef.current.open;
|
||||
const hasActivePromptDialog = () =>
|
||||
textPromptDialogRef.current.open
|
||||
|| choicePromptDialogRef.current.open
|
||||
|| fastgptInteractiveDialogRef.current.open;
|
||||
|
||||
const buildFastGPTFieldValues = (
|
||||
fields: DebugFastGPTInteractiveField[],
|
||||
initialValues?: Record<string, string>
|
||||
): Record<string, string> => {
|
||||
const nextValues: Record<string, string> = {};
|
||||
fields.forEach((field) => {
|
||||
const initial = initialValues?.[field.name];
|
||||
if (initial !== undefined) {
|
||||
nextValues[field.name] = initial;
|
||||
return;
|
||||
}
|
||||
if (field.defaultValue !== undefined) {
|
||||
nextValues[field.name] = field.defaultValue;
|
||||
return;
|
||||
}
|
||||
if (field.options.length > 0 && ['select', 'dropdown', 'radio'].includes(field.inputType.toLowerCase())) {
|
||||
nextValues[field.name] = field.options[0]?.value || '';
|
||||
return;
|
||||
}
|
||||
nextValues[field.name] = '';
|
||||
});
|
||||
return nextValues;
|
||||
};
|
||||
|
||||
const activatePromptDialog = (item: DebugPromptQueueItem) => {
|
||||
if (item.kind === 'text') {
|
||||
@@ -2882,6 +3069,30 @@ export const DebugDrawer: React.FC<{
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (item.kind === 'fastgpt') {
|
||||
const nextVoiceText = String(item.payload.prompt || item.payload.description || item.payload.title || '').trim();
|
||||
const normalizedForm = item.payload.form || [];
|
||||
setFastgptInteractiveDialog({
|
||||
open: true,
|
||||
interactionType: item.payload.interactionType,
|
||||
title: item.payload.title,
|
||||
description: item.payload.description,
|
||||
prompt: item.payload.prompt,
|
||||
options: item.payload.options,
|
||||
form: normalizedForm,
|
||||
multiple: item.payload.multiple,
|
||||
required: item.payload.required,
|
||||
selectedValues: item.payload.selectedValues || [],
|
||||
fieldValues: buildFastGPTFieldValues(normalizedForm, item.payload.fieldValues),
|
||||
pendingResult: item.payload.pendingResult,
|
||||
submitLabel: item.payload.submitLabel,
|
||||
cancelLabel: item.payload.cancelLabel,
|
||||
});
|
||||
if (nextVoiceText) {
|
||||
void playPromptVoice(nextVoiceText);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const nextVoiceText = String(item.payload.voiceText || '').trim();
|
||||
setChoicePromptDialog({
|
||||
open: true,
|
||||
@@ -2940,6 +3151,111 @@ export const DebugDrawer: React.FC<{
|
||||
}
|
||||
};
|
||||
|
||||
const closeFastGPTInteractiveDialog = (
|
||||
action: 'submit' | 'cancel',
|
||||
opts?: { force?: boolean; skipQueueAdvance?: boolean }
|
||||
) => {
|
||||
const snapshot = fastgptInteractiveDialogRef.current;
|
||||
if (!snapshot.open && !opts?.force) return;
|
||||
|
||||
const pending = snapshot.pendingResult;
|
||||
const selectedValues = snapshot.selectedValues;
|
||||
const fieldValues = snapshot.fieldValues;
|
||||
const interactionType = snapshot.interactionType;
|
||||
stopPromptVoicePlayback();
|
||||
setFastgptInteractiveDialog({
|
||||
open: false,
|
||||
interactionType: 'userSelect',
|
||||
title: '',
|
||||
description: '',
|
||||
prompt: '',
|
||||
options: [],
|
||||
form: [],
|
||||
multiple: false,
|
||||
required: true,
|
||||
selectedValues: [],
|
||||
fieldValues: {},
|
||||
submitLabel: 'Continue',
|
||||
cancelLabel: 'Cancel',
|
||||
});
|
||||
|
||||
if (pending?.waitForResponse) {
|
||||
const primarySelected = selectedValues[0] || '';
|
||||
emitClientToolResult(
|
||||
{
|
||||
tool_call_id: pending.toolCallId,
|
||||
name: pending.toolName,
|
||||
output: action === 'cancel'
|
||||
? {
|
||||
version: 'fastgpt_interactive_v1',
|
||||
action: 'cancel',
|
||||
result: {},
|
||||
}
|
||||
: {
|
||||
version: 'fastgpt_interactive_v1',
|
||||
action: 'submit',
|
||||
result: {
|
||||
type: interactionType,
|
||||
selected: interactionType === 'userSelect' && !snapshot.multiple ? primarySelected : '',
|
||||
selected_values: interactionType === 'userSelect' ? selectedValues : [],
|
||||
fields: interactionType === 'userInput' ? fieldValues : {},
|
||||
text: '',
|
||||
},
|
||||
},
|
||||
status: action === 'cancel'
|
||||
? { code: 499, message: 'user_cancelled' }
|
||||
: { code: 200, message: 'ok' },
|
||||
},
|
||||
pending.toolDisplayName
|
||||
);
|
||||
}
|
||||
if (!opts?.skipQueueAdvance) {
|
||||
openNextPromptDialog(true);
|
||||
}
|
||||
};
|
||||
|
||||
const updateFastGPTFieldValue = (fieldName: string, value: string) => {
|
||||
setFastgptInteractiveDialog((prev) => ({
|
||||
...prev,
|
||||
fieldValues: {
|
||||
...prev.fieldValues,
|
||||
[fieldName]: value,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const toggleFastGPTSelectedValue = (optionValue: string) => {
|
||||
setFastgptInteractiveDialog((prev) => {
|
||||
if (!prev.multiple) {
|
||||
return { ...prev, selectedValues: [optionValue] };
|
||||
}
|
||||
const hasValue = prev.selectedValues.includes(optionValue);
|
||||
return {
|
||||
...prev,
|
||||
selectedValues: hasValue
|
||||
? prev.selectedValues.filter((item) => item !== optionValue)
|
||||
: [...prev.selectedValues, optionValue],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const canSubmitFastGPTInteractiveDialog = (snapshot: DebugFastGPTInteractiveDialogState = fastgptInteractiveDialog) => {
|
||||
if (!snapshot.open) return false;
|
||||
if (snapshot.interactionType === 'userSelect') {
|
||||
if (!snapshot.required) return true;
|
||||
return snapshot.selectedValues.length > 0;
|
||||
}
|
||||
return snapshot.form.every((field) => {
|
||||
if (!field.required) return true;
|
||||
return String(snapshot.fieldValues[field.name] || '').trim().length > 0;
|
||||
});
|
||||
};
|
||||
|
||||
const fastgptInteractiveHeaderText = fastgptInteractiveDialog.title
|
||||
|| fastgptInteractiveDialog.description
|
||||
|| fastgptInteractiveDialog.prompt
|
||||
|| 'FastGPT';
|
||||
|
||||
const closeChoicePromptDialog = (
|
||||
action: 'select' | 'dismiss',
|
||||
selectedOption?: DebugChoicePromptOption,
|
||||
@@ -3844,6 +4160,71 @@ export const DebugDrawer: React.FC<{
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (toolName === 'fastgpt.interactive') {
|
||||
const interaction = parsedArgs?.interaction && typeof parsedArgs.interaction === 'object'
|
||||
? parsedArgs.interaction as Record<string, any>
|
||||
: {};
|
||||
const interactionType = interaction?.type === 'userInput' ? 'userInput' : 'userSelect';
|
||||
const options = normalizeFastGPTInteractiveOptions(
|
||||
Array.isArray(interaction?.options) ? interaction.options : []
|
||||
);
|
||||
const form = normalizeFastGPTInteractiveFields(
|
||||
Array.isArray(interaction?.form) ? interaction.form : []
|
||||
);
|
||||
const title = String(interaction?.title || '').trim();
|
||||
const description = String(interaction?.description || '').trim();
|
||||
const prompt = String(interaction?.prompt || title || description || '').trim();
|
||||
const submitLabel = String(interaction?.submit_label || interaction?.submitLabel || 'Continue').trim() || 'Continue';
|
||||
const cancelLabel = String(interaction?.cancel_label || interaction?.cancelLabel || 'Cancel').trim() || 'Cancel';
|
||||
const multiple = Boolean(interaction?.multiple);
|
||||
const required = interaction?.required === undefined ? true : Boolean(interaction.required);
|
||||
|
||||
if (interactionType === 'userSelect' && options.length === 0) {
|
||||
resultPayload.output = { message: "Argument 'interaction.options' requires at least 1 valid entry" };
|
||||
resultPayload.status = { code: 422, message: 'invalid_arguments' };
|
||||
} else if (interactionType === 'userInput' && form.length === 0) {
|
||||
resultPayload.output = { message: "Argument 'interaction.form' requires at least 1 valid entry" };
|
||||
resultPayload.status = { code: 422, message: 'invalid_arguments' };
|
||||
} else {
|
||||
enqueuePromptDialog({
|
||||
kind: 'fastgpt',
|
||||
payload: {
|
||||
interactionType,
|
||||
title,
|
||||
description,
|
||||
prompt,
|
||||
options,
|
||||
form,
|
||||
multiple,
|
||||
required,
|
||||
pendingResult: {
|
||||
toolCallId: toolCallId,
|
||||
toolName,
|
||||
toolDisplayName,
|
||||
waitForResponse,
|
||||
},
|
||||
submitLabel,
|
||||
cancelLabel,
|
||||
},
|
||||
});
|
||||
if (waitForResponse) {
|
||||
return;
|
||||
}
|
||||
resultPayload.output = {
|
||||
message: 'fastgpt_interactive_shown',
|
||||
interaction: {
|
||||
type: interactionType,
|
||||
title,
|
||||
description,
|
||||
prompt,
|
||||
options,
|
||||
form,
|
||||
multiple,
|
||||
required,
|
||||
},
|
||||
};
|
||||
resultPayload.status = { code: 200, message: 'ok' };
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
resultPayload.output = {
|
||||
@@ -4568,9 +4949,8 @@ export const DebugDrawer: React.FC<{
|
||||
<div className="w-full flex justify-center items-center">
|
||||
{mode === 'text' && textSessionStarted && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="lg"
|
||||
className="w-full font-bold shadow-lg shadow-destructive/20 hover:shadow-destructive/40 transition-all"
|
||||
className="w-full h-12 rounded-full border-0 bg-red-500 text-base font-bold text-white shadow-[0_0_20px_rgba(239,68,68,0.35)] hover:bg-red-600 hover:shadow-[0_0_24px_rgba(220,38,38,0.45)] active:translate-y-px focus-visible:ring-red-400/40"
|
||||
onClick={closeWs}
|
||||
>
|
||||
<PhoneOff className="h-5 w-5 mr-2" />
|
||||
@@ -4579,9 +4959,8 @@ export const DebugDrawer: React.FC<{
|
||||
)}
|
||||
{mode !== 'text' && callStatus === 'active' && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="lg"
|
||||
className="w-full font-bold shadow-lg shadow-destructive/20 hover:shadow-destructive/40 transition-all"
|
||||
className="w-full h-12 rounded-full border-0 bg-red-500 text-base font-bold text-white shadow-[0_0_20px_rgba(239,68,68,0.35)] hover:bg-red-600 hover:shadow-[0_0_24px_rgba(220,38,38,0.45)] active:translate-y-px focus-visible:ring-red-400/40"
|
||||
onClick={handleHangup}
|
||||
>
|
||||
<PhoneOff className="h-5 w-5 mr-2" />
|
||||
@@ -4705,6 +5084,143 @@ export const DebugDrawer: React.FC<{
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{fastgptInteractiveDialog.open && (
|
||||
<div className="absolute inset-0 z-40 flex items-center justify-center bg-black/55 backdrop-blur-[1px]">
|
||||
<div className="relative w-[92%] max-w-lg rounded-xl border border-white/15 bg-card/95 p-4 shadow-2xl animate-in zoom-in-95 duration-200">
|
||||
{!fastgptInteractiveDialog.required && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => closeFastGPTInteractiveDialog('cancel')}
|
||||
className="absolute right-3 top-3 rounded-sm opacity-70 hover:opacity-100 text-muted-foreground hover:text-foreground transition-opacity"
|
||||
title="关闭"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
<div className="mb-4 pr-6">
|
||||
<div className="text-[10px] font-black tracking-[0.14em] uppercase text-cyan-300">
|
||||
{fastgptInteractiveHeaderText}
|
||||
</div>
|
||||
{fastgptInteractiveDialog.prompt
|
||||
&& fastgptInteractiveDialog.prompt !== fastgptInteractiveHeaderText && (
|
||||
<h3 className="mt-2 text-base font-semibold text-foreground">
|
||||
{fastgptInteractiveDialog.prompt}
|
||||
</h3>
|
||||
)}
|
||||
{fastgptInteractiveDialog.description
|
||||
&& fastgptInteractiveDialog.description !== fastgptInteractiveHeaderText
|
||||
&& fastgptInteractiveDialog.description !== fastgptInteractiveDialog.prompt && (
|
||||
<p className="mt-2 text-sm leading-6 text-foreground/90 whitespace-pre-wrap break-words">
|
||||
{fastgptInteractiveDialog.description}
|
||||
</p>
|
||||
)}
|
||||
{fastgptInteractiveDialog.prompt
|
||||
&& fastgptInteractiveDialog.prompt !== fastgptInteractiveHeaderText
|
||||
&& fastgptInteractiveDialog.prompt !== fastgptInteractiveDialog.description && (
|
||||
<p className="mt-2 text-sm leading-6 text-foreground whitespace-pre-wrap break-words">
|
||||
{fastgptInteractiveDialog.prompt}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{fastgptInteractiveDialog.interactionType === 'userSelect' ? (
|
||||
<div className="space-y-2">
|
||||
{fastgptInteractiveDialog.options.map((option) => {
|
||||
const selected = fastgptInteractiveDialog.selectedValues.includes(option.value);
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
onClick={() => toggleFastGPTSelectedValue(option.value)}
|
||||
className={`w-full rounded-lg border px-3 py-3 text-left transition-colors ${selected
|
||||
? 'border-primary/50 bg-primary/10 text-foreground'
|
||||
: 'border-white/10 bg-black/10 text-foreground hover:border-primary/30 hover:bg-white/5'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div
|
||||
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full border ${selected ? 'border-primary bg-primary/20' : 'border-white/25'
|
||||
}`}
|
||||
>
|
||||
<div className={`h-2 w-2 rounded-full ${selected ? 'bg-primary' : 'bg-transparent'}`} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-medium">{option.label}</div>
|
||||
{option.description && (
|
||||
<div className="mt-1 text-xs text-muted-foreground">{option.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{fastgptInteractiveDialog.form.map((field) => {
|
||||
const value = fastgptInteractiveDialog.fieldValues[field.name] || '';
|
||||
const fieldType = field.inputType.toLowerCase();
|
||||
const useTextarea = ['textarea', 'multiline', 'longtext'].includes(fieldType);
|
||||
const useSelect = field.options.length > 0 && ['select', 'dropdown', 'radio'].includes(fieldType);
|
||||
return (
|
||||
<label key={field.name} className="block space-y-1.5">
|
||||
<div className="text-xs font-medium text-foreground/90">
|
||||
{field.label}
|
||||
{field.required && <span className="ml-1 text-rose-300">*</span>}
|
||||
</div>
|
||||
{useTextarea ? (
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(event) => updateFastGPTFieldValue(field.name, event.target.value)}
|
||||
placeholder={field.placeholder || ''}
|
||||
rows={4}
|
||||
className="min-h-[96px] w-full rounded-md border border-white/10 bg-black/20 px-3 py-2 text-sm text-foreground outline-none transition-colors placeholder:text-muted-foreground focus:border-primary/50 focus:ring-1 focus:ring-primary/40"
|
||||
/>
|
||||
) : useSelect ? (
|
||||
<select
|
||||
value={value}
|
||||
onChange={(event) => updateFastGPTFieldValue(field.name, event.target.value)}
|
||||
className="flex h-9 w-full rounded-md border border-white/10 bg-black/20 px-3 py-1 text-sm text-foreground shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/40 [&>option]:bg-card [&>option]:text-foreground"
|
||||
>
|
||||
{!field.required && <option value="">请选择</option>}
|
||||
{field.options.map((option) => (
|
||||
<option key={option.id} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<Input
|
||||
type={fieldType === 'number' ? 'number' : fieldType === 'email' ? 'email' : 'text'}
|
||||
value={value}
|
||||
onChange={(event) => updateFastGPTFieldValue(field.name, event.target.value)}
|
||||
placeholder={field.placeholder || ''}
|
||||
className="border-white/10 bg-black/20"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-4 flex items-center justify-end gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => closeFastGPTInteractiveDialog('cancel')}
|
||||
>
|
||||
{fastgptInteractiveDialog.cancelLabel || 'Cancel'}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => closeFastGPTInteractiveDialog('submit')}
|
||||
disabled={!canSubmitFastGPTInteractiveDialog(fastgptInteractiveDialog)}
|
||||
>
|
||||
{fastgptInteractiveDialog.submitLabel || 'Continue'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Drawer>
|
||||
{isOpen && (
|
||||
<Dialog
|
||||
|
||||
@@ -95,6 +95,7 @@ const mapAssistant = (raw: AnyRecord): Assistant => ({
|
||||
configMode: readField(raw, ['configMode', 'config_mode'], 'platform') as 'platform' | 'dify' | 'fastgpt' | 'none',
|
||||
apiUrl: readField(raw, ['apiUrl', 'api_url'], ''),
|
||||
apiKey: readField(raw, ['apiKey', 'api_key'], ''),
|
||||
appId: readField(raw, ['appId', 'app_id'], ''),
|
||||
llmModelId: readField(raw, ['llmModelId', 'llm_model_id'], ''),
|
||||
asrModelId: readField(raw, ['asrModelId', 'asr_model_id'], ''),
|
||||
embeddingModelId: readField(raw, ['embeddingModelId', 'embedding_model_id'], ''),
|
||||
@@ -302,6 +303,7 @@ export const createAssistant = async (data: Partial<Assistant>): Promise<Assista
|
||||
configMode: data.configMode || 'platform',
|
||||
apiUrl: data.apiUrl || '',
|
||||
apiKey: data.apiKey || '',
|
||||
appId: data.appId || '',
|
||||
llmModelId: data.llmModelId || '',
|
||||
asrModelId: data.asrModelId || '',
|
||||
embeddingModelId: data.embeddingModelId || '',
|
||||
@@ -335,6 +337,7 @@ export const updateAssistant = async (id: string, data: Partial<Assistant>): Pro
|
||||
configMode: data.configMode,
|
||||
apiUrl: data.apiUrl,
|
||||
apiKey: data.apiKey,
|
||||
appId: data.appId,
|
||||
llmModelId: data.llmModelId,
|
||||
asrModelId: data.asrModelId,
|
||||
embeddingModelId: data.embeddingModelId,
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface Assistant {
|
||||
configMode?: 'platform' | 'dify' | 'fastgpt' | 'none';
|
||||
apiUrl?: string;
|
||||
apiKey?: string;
|
||||
appId?: string;
|
||||
llmModelId?: string;
|
||||
asrModelId?: string;
|
||||
embeddingModelId?: string;
|
||||
|
||||
Reference in New Issue
Block a user