Spaces:
Running
Running
can you add suppport for streaming? and then add a tailwind modern tick box in custom model setting called Streaming to toggle if the model supports streaming - Follow Up Deployment
Browse files- index.html +110 -32
index.html
CHANGED
|
@@ -214,6 +214,11 @@
|
|
| 214 |
<label for="modelKey" class="block text-sm font-medium mb-1">API Key (optional)</label>
|
| 215 |
<input type="password" id="modelKey" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 focus:ring-primary-500 focus:border-primary-500" placeholder="sk-...">
|
| 216 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
<input type="hidden" id="editModelId" value="">
|
| 218 |
|
| 219 |
<div class="flex justify-end gap-2">
|
|
@@ -677,37 +682,106 @@
|
|
| 677 |
|
| 678 |
// Call custom model API
|
| 679 |
async function callCustomModelAPI(messages, model) {
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
throw new Error(errorData.error?.message || 'Failed to call custom model API');
|
| 697 |
-
} catch (e) {
|
| 698 |
-
// If JSON parsing fails, try to get the raw error text
|
| 699 |
const errorText = await response.text();
|
| 700 |
throw new Error(errorText || 'Failed to call custom model API');
|
| 701 |
}
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 711 |
}
|
| 712 |
}
|
| 713 |
|
|
@@ -734,12 +808,13 @@
|
|
| 734 |
}
|
| 735 |
|
| 736 |
// Add custom model
|
| 737 |
-
function addCustomModel(name, endpoint, key) {
|
| 738 |
const newModel = {
|
| 739 |
id: `custom-${Date.now()}`,
|
| 740 |
name,
|
| 741 |
endpoint,
|
| 742 |
key,
|
|
|
|
| 743 |
type: 'custom'
|
| 744 |
};
|
| 745 |
|
|
@@ -755,7 +830,7 @@
|
|
| 755 |
}
|
| 756 |
|
| 757 |
// Edit custom model
|
| 758 |
-
function editCustomModel(modelId, name, endpoint, key) {
|
| 759 |
const modelIndex = customModels.findIndex(m => m.id === modelId);
|
| 760 |
if (modelIndex === -1) return;
|
| 761 |
|
|
@@ -763,7 +838,8 @@
|
|
| 763 |
...customModels[modelIndex],
|
| 764 |
name,
|
| 765 |
endpoint,
|
| 766 |
-
key
|
|
|
|
| 767 |
};
|
| 768 |
|
| 769 |
// Update in models array
|
|
@@ -839,6 +915,7 @@
|
|
| 839 |
document.getElementById('modelName').value = model.name;
|
| 840 |
document.getElementById('modelEndpoint').value = model.endpoint;
|
| 841 |
document.getElementById('modelKey').value = model.key || '';
|
|
|
|
| 842 |
document.getElementById('editModelId').value = model.id;
|
| 843 |
|
| 844 |
// Change modal title and submit button text
|
|
@@ -983,10 +1060,11 @@
|
|
| 983 |
const name = document.getElementById('modelName').value;
|
| 984 |
const endpoint = document.getElementById('modelEndpoint').value;
|
| 985 |
const key = document.getElementById('modelKey').value;
|
|
|
|
| 986 |
const editModelId = document.getElementById('editModelId').value;
|
| 987 |
|
| 988 |
if (editModelId) {
|
| 989 |
-
editCustomModel(editModelId, name, endpoint, key);
|
| 990 |
} else {
|
| 991 |
addCustomModel(name, endpoint, key);
|
| 992 |
}
|
|
|
|
| 214 |
<label for="modelKey" class="block text-sm font-medium mb-1">API Key (optional)</label>
|
| 215 |
<input type="password" id="modelKey" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 focus:ring-primary-500 focus:border-primary-500" placeholder="sk-...">
|
| 216 |
</div>
|
| 217 |
+
|
| 218 |
+
<div class="mb-4 flex items-center">
|
| 219 |
+
<input type="checkbox" id="modelStreaming" class="w-4 h-4 text-primary-600 rounded border-gray-300 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700">
|
| 220 |
+
<label for="modelStreaming" class="ml-2 text-sm font-medium text-gray-700 dark:text-gray-300">Supports Streaming</label>
|
| 221 |
+
</div>
|
| 222 |
<input type="hidden" id="editModelId" value="">
|
| 223 |
|
| 224 |
<div class="flex justify-end gap-2">
|
|
|
|
| 682 |
|
| 683 |
// Call custom model API
|
| 684 |
async function callCustomModelAPI(messages, model) {
|
| 685 |
+
if (model.streaming) {
|
| 686 |
+
// Handle streaming response
|
| 687 |
+
const response = await fetch(model.endpoint, {
|
| 688 |
+
method: 'POST',
|
| 689 |
+
headers: {
|
| 690 |
+
'Content-Type': 'application/json',
|
| 691 |
+
...(model.key ? { 'Authorization': `Bearer ${model.key}` } : {})
|
| 692 |
+
},
|
| 693 |
+
body: JSON.stringify({
|
| 694 |
+
messages,
|
| 695 |
+
temperature: 0.7,
|
| 696 |
+
stream: true
|
| 697 |
+
})
|
| 698 |
+
});
|
| 699 |
+
|
| 700 |
+
if (!response.ok) {
|
|
|
|
|
|
|
|
|
|
| 701 |
const errorText = await response.text();
|
| 702 |
throw new Error(errorText || 'Failed to call custom model API');
|
| 703 |
}
|
| 704 |
+
|
| 705 |
+
const reader = response.body.getReader();
|
| 706 |
+
const decoder = new TextDecoder();
|
| 707 |
+
let result = '';
|
| 708 |
+
|
| 709 |
+
// Create message element for streaming
|
| 710 |
+
const messageElement = document.createElement('div');
|
| 711 |
+
messageElement.className = 'flex gap-4 justify-start';
|
| 712 |
+
messageElement.innerHTML = `
|
| 713 |
+
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-primary-500 text-white flex items-center justify-center">
|
| 714 |
+
<i class="fas fa-robot"></i>
|
| 715 |
+
</div>
|
| 716 |
+
<div class="max-w-[80%] bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm relative group">
|
| 717 |
+
<div class="prose dark:prose-invert" id="streaming-content"></div>
|
| 718 |
+
</div>
|
| 719 |
+
`;
|
| 720 |
+
chatArea.appendChild(messageElement);
|
| 721 |
+
const contentElement = messageElement.querySelector('#streaming-content');
|
| 722 |
+
|
| 723 |
+
try {
|
| 724 |
+
while (true) {
|
| 725 |
+
const { done, value } = await reader.read();
|
| 726 |
+
if (done) break;
|
| 727 |
+
|
| 728 |
+
const chunk = decoder.decode(value);
|
| 729 |
+
const lines = chunk.split('\n').filter(line => line.trim() !== '');
|
| 730 |
+
|
| 731 |
+
for (const line of lines) {
|
| 732 |
+
if (line.startsWith('data: ')) {
|
| 733 |
+
const data = line.substring(6);
|
| 734 |
+
if (data === '[DONE]') continue;
|
| 735 |
+
|
| 736 |
+
try {
|
| 737 |
+
const parsed = JSON.parse(data);
|
| 738 |
+
const content = parsed.choices?.[0]?.delta?.content || '';
|
| 739 |
+
result += content;
|
| 740 |
+
contentElement.innerHTML = result;
|
| 741 |
+
chatArea.scrollTop = chatArea.scrollHeight;
|
| 742 |
+
} catch (e) {
|
| 743 |
+
console.error('Error parsing stream data:', e);
|
| 744 |
+
}
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
} catch (e) {
|
| 749 |
+
console.error('Stream error:', e);
|
| 750 |
+
throw e;
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
return result;
|
| 754 |
+
} else {
|
| 755 |
+
// Handle regular response
|
| 756 |
+
const response = await fetch(model.endpoint, {
|
| 757 |
+
method: 'POST',
|
| 758 |
+
headers: {
|
| 759 |
+
'Content-Type': 'application/json',
|
| 760 |
+
...(model.key ? { 'Authorization': `Bearer ${model.key}` } : {})
|
| 761 |
+
},
|
| 762 |
+
body: JSON.stringify({
|
| 763 |
+
messages,
|
| 764 |
+
temperature: 0.7,
|
| 765 |
+
})
|
| 766 |
+
});
|
| 767 |
+
|
| 768 |
+
if (!response.ok) {
|
| 769 |
+
try {
|
| 770 |
+
const errorData = await response.json();
|
| 771 |
+
throw new Error(errorData.error?.message || 'Failed to call custom model API');
|
| 772 |
+
} catch (e) {
|
| 773 |
+
const errorText = await response.text();
|
| 774 |
+
throw new Error(errorText || 'Failed to call custom model API');
|
| 775 |
+
}
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
try {
|
| 779 |
+
const data = await response.json();
|
| 780 |
+
return data.choices[0]?.message?.content || data.response || 'No response from model';
|
| 781 |
+
} catch (e) {
|
| 782 |
+
const text = await response.text();
|
| 783 |
+
return text || 'No response from model';
|
| 784 |
+
}
|
| 785 |
}
|
| 786 |
}
|
| 787 |
|
|
|
|
| 808 |
}
|
| 809 |
|
| 810 |
// Add custom model
|
| 811 |
+
function addCustomModel(name, endpoint, key, streaming) {
|
| 812 |
const newModel = {
|
| 813 |
id: `custom-${Date.now()}`,
|
| 814 |
name,
|
| 815 |
endpoint,
|
| 816 |
key,
|
| 817 |
+
streaming: streaming || false,
|
| 818 |
type: 'custom'
|
| 819 |
};
|
| 820 |
|
|
|
|
| 830 |
}
|
| 831 |
|
| 832 |
// Edit custom model
|
| 833 |
+
function editCustomModel(modelId, name, endpoint, key, streaming) {
|
| 834 |
const modelIndex = customModels.findIndex(m => m.id === modelId);
|
| 835 |
if (modelIndex === -1) return;
|
| 836 |
|
|
|
|
| 838 |
...customModels[modelIndex],
|
| 839 |
name,
|
| 840 |
endpoint,
|
| 841 |
+
key,
|
| 842 |
+
streaming: streaming || false
|
| 843 |
};
|
| 844 |
|
| 845 |
// Update in models array
|
|
|
|
| 915 |
document.getElementById('modelName').value = model.name;
|
| 916 |
document.getElementById('modelEndpoint').value = model.endpoint;
|
| 917 |
document.getElementById('modelKey').value = model.key || '';
|
| 918 |
+
document.getElementById('modelStreaming').checked = model.streaming || false;
|
| 919 |
document.getElementById('editModelId').value = model.id;
|
| 920 |
|
| 921 |
// Change modal title and submit button text
|
|
|
|
| 1060 |
const name = document.getElementById('modelName').value;
|
| 1061 |
const endpoint = document.getElementById('modelEndpoint').value;
|
| 1062 |
const key = document.getElementById('modelKey').value;
|
| 1063 |
+
const streaming = document.getElementById('modelStreaming').checked;
|
| 1064 |
const editModelId = document.getElementById('editModelId').value;
|
| 1065 |
|
| 1066 |
if (editModelId) {
|
| 1067 |
+
editCustomModel(editModelId, name, endpoint, key, streaming);
|
| 1068 |
} else {
|
| 1069 |
addCustomModel(name, endpoint, key);
|
| 1070 |
}
|