CoolFace
Apppublic

pvanand/code-chat-api

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
index.html358 linesDownload Raw Back to chatui
1<!DOCTYPE html>2<html lang="en">3<head>4    <meta charset="UTF-8">5    <meta name="viewport" content="width=device-width, initial-scale=1.0">6    <title>Agent Chat Interface</title>7    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>8    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">9    <link href="https://cdn.jsdelivr.net/npm/@tailwindcss/typography/dist/typography.min.css" rel="stylesheet">10    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>11    <script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>12    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">13    <style>14        .chat-message {15            transition: opacity 0.3s ease, transform 0.3s ease;16        }17        .chat-message-enter {18            opacity: 0;19            transform: translateY(10px);20        }21        .chat-message-enter-active {22            opacity: 1;23            transform: translateY(0);24        }25        .tool-section {26            transition: max-height 0.3s ease, opacity 0.3s ease;27        }28        .tool-section.collapsed {29            max-height: 0;30            opacity: 0;31            overflow: hidden;32        }33        .tool-section.expanded {34            max-height: 1000px;35            opacity: 1;36        }37        pre code {38            display: block;39            background: #f5f5f5;40            padding: 1rem;41            border-radius: 0.5rem;42            overflow-x: auto;43        }44    </style>45</head>46<body class="bg-gray-50 font-inter">47    <div id="app" class="min-h-screen flex flex-col">48        <div class="max-w-4xl mx-auto p-4 flex-1 flex flex-col w-full">49            <!-- Chat Messages -->50            <div class="bg-white rounded-lg shadow-lg p-6 mb-4 flex-1 overflow-y-auto w-full">51                <div v-for="(message, index) in messages" :key="index" class="chat-message mb-4">52                    <!-- User Message -->53                    <div v-if="message.role === 'user'" class="flex justify-end">54                        <div class="bg-blue-500 text-white rounded-lg py-2 px-4 max-w-[80%] shadow-sm">55                            {{ message.content }}56                        </div>57                    </div>58 59                    <!-- Assistant Message -->60                    <div v-else class="flex flex-col space-y-2">61                        <!-- Regular Message -->62                        <div v-if="message.content" class="bg-gray-100 rounded-lg py-2 px-4 max-w-[80%] shadow-sm prose">63                            <div v-html="formatMarkdown(message.content)"></div>64                        </div>65 66                        <!-- Tool Execution -->67                        <div v-if="message.tool_data" class="border border-gray-200 rounded-lg p-4 max-w-[80%] shadow-sm">68                            <div class="flex items-center justify-between cursor-pointer" 69                                 @click="toggleTool(index)">70                                <h3 class="font-semibold text-gray-700">71                                    Tool: {{ message.tool_data.tool }}72                                </h3>73                                <span class="text-gray-500">74                                    {{ isToolExpanded(index) ? '▼' : '▶' }}75                                </span>76                            </div>77                            78                            <div :class="['tool-section', isToolExpanded(index) ? 'expanded' : 'collapsed']">79                                <!-- Tool Input -->80                                <div v-if="message.tool_data.input" class="mt-2">81                                    <div class="text-sm text-gray-600">Input:</div>82                                    <pre v-if="message.tool_data.tool === 'execute_python'"><code>{{ message.tool_data.input.code }}</code></pre>83                                    <pre v-else class="bg-gray-50 p-2 rounded mt-1 text-sm overflow-x-auto">{{ JSON.stringify(message.tool_data.input, null, 2) }}</pre>84                                </div>85 86                                <!-- Tool Output -->87                                <div v-if="message.tool_data.output" class="mt-2">88                                    <div class="text-sm text-gray-600">Output:</div>89                                    <div v-if="message.tool_data.output.artifacts" class="space-y-4">90                                        <div v-for="(artifact, artifactIndex) in message.tool_data.output.artifacts" 91                                             :key="artifact.artifact_id"92                                             class="mt-4">93                                            <!-- Image Artifact -->94                                            <img v-if="artifact.artifact_type.startsWith('image/')"95                                                 :src="artifact.imageData ? `data:${artifact.artifact_type};base64,${artifact.imageData}` : ''"96                                                 class="max-w-full h-auto rounded shadow-sm"97                                                 :alt="artifact.artifact_id">98                                            99                                            <!-- Plotly Artifact -->100                                            <div v-else-if="artifact.artifact_type === 'application/vnd.plotly.v1+json'"101                                                 :id="'plot-' + index + '-' + artifactIndex"102                                                 class="w-full h-96">103                                            </div>104 105                                            <!-- HTML Artifact -->106                                            <div v-else-if="artifact.artifact_type === 'text/html'"107                                                 class="border rounded p-2 bg-gray-50 shadow-sm"108                                                 v-html="artifact.content">109                                            </div>110                                        </div>111                                    </div>112                                    <pre v-if="message.tool_data.output.text" 113                                         class="bg-gray-50 p-2 rounded mt-1 text-sm overflow-x-auto shadow-sm">{{ message.tool_data.output.text }}</pre>114                                </div>115                            </div>116                        </div>117                    </div>118                </div>119            </div>120 121            <!-- Input Area -->122            <div class="flex space-x-4">123                <input v-model="userInput"124                       @keyup.enter="sendMessage"125                       type="text"126                       class="flex-1 rounded-lg border border-gray-300 p-2 focus:outline-none focus:ring-2 focus:ring-blue-500 shadow-sm"127                       placeholder="Type your message...">128                <button @click="sendMessage"129                        class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 shadow-sm">130                    Send131                </button>132            </div>133        </div>134    </div>135 136    <script>137        const { createApp } = Vue138 139        createApp({140            data() {141                return {142                    messages: [],143                    userInput: '',144                    expandedTools: new Set(),145                    currentAssistantMessage: '',146                    session_token: crypto.randomUUID()147                }148            },149            methods: {150                formatMarkdown(text) {151                    return marked.parse(text)152                },153                toggleTool(index) {154                    if (this.expandedTools.has(index)) {155                        this.expandedTools.delete(index)156                    } else {157                        this.expandedTools.add(index)158                        // Re-render Plotly charts when expanding, only if they haven't been rendered159                        this.$nextTick(() => {160                            const message = this.messages[index]161                            if (message?.tool_data?.output?.artifacts) {162                                const needsRendering = message.tool_data.output.artifacts.some(artifact => {163                                    if (artifact.artifact_type === 'application/vnd.plotly.v1+json') {164                                        const elementId = `plot-${index}-${message.tool_data.output.artifacts.indexOf(artifact)}`165                                        const element = document.getElementById(elementId)166                                        return element && !element._fullData167                                    }168                                    return false169                                })170                                if (needsRendering) {171                                    this.renderPlotlyCharts(index)172                                }173                            }174                        })175                    }176                },177                isToolExpanded(index) {178                    return this.expandedTools.has(index)179                },180                async fetchArtifact(artifactId) {181                    try {182                        const response = await fetch(`https://pvanand-code-execution-files-v5.hf.space/artifact/${artifactId}`)183                        if (!response.ok) throw new Error('Failed to fetch artifact')184                        const data = await response.json()185                        return data186                    } catch (error) {187                        console.error('Error fetching artifact:', error)188                        return null189                    }190                },191                renderPlotlyCharts(messageIndex) {192                    const message = this.messages[messageIndex]193                    if (!message?.tool_data?.output?.artifacts) return194 195                    message.tool_data.output.artifacts.forEach((artifact, artifactIndex) => {196                        if (artifact.artifact_type === 'application/vnd.plotly.v1+json' && artifact.plotData) {197                            const elementId = `plot-${messageIndex}-${artifactIndex}`198                            const element = document.getElementById(elementId)199                            if (element) {200                                // Check if a plot already exists in this element201                                if (element._fullData) {202                                    // Update existing plot203                                    Plotly.react(elementId, artifact.plotData)204                                } else {205                                    // Create new plot206                                    Plotly.newPlot(elementId, artifact.plotData)207                                }208                            }209                        }210                    })211                },212                async handleToolEnd(eventData) {213                    // Create a new message for each tool output214                    const newMessage = {215                        role: 'assistant',216                        tool_data: {217                            tool: eventData.tool,218                            output: {219                                text: eventData.output.text,220                                artifacts: []221                            }222                        }223                    }224 225                    if (eventData.output?.artifacts) {226                        for (const artifact of eventData.output.artifacts) {227                            const data = await this.fetchArtifact(artifact.artifact_id)228                            229                            if (artifact.artifact_type.startsWith('image/')) {230                                artifact.imageData = data.data231                            }232                            else if (artifact.artifact_type === 'application/vnd.plotly.v1+json') {233                                artifact.plotData = JSON.parse(data.data)234                            }235                            else if (artifact.artifact_type === 'text/html') {236                                artifact.content = data.data237                            }238                            newMessage.tool_data.output.artifacts.push(artifact)239                        }240                    }241 242                    this.messages.push(newMessage)243                    // Expand the tool section automatically244                    this.expandedTools.add(this.messages.length - 1)245                    // Render Plotly charts after the DOM updates246                    this.$nextTick(() => {247                        this.renderPlotlyCharts(this.messages.length - 1)248                    })249                },250                async sendMessage() {251                    if (!this.userInput.trim()) return252 253                    // Add user message254                    this.messages.push({255                        role: 'user',256                        content: this.userInput257                    })258 259                    const data = {260                        message: this.userInput,261                        thread_id: this.session_token262                    }263 264                    this.userInput = ''265                    this.currentAssistantMessage = ''266 267                    try {268                        const response = await fetch('https://pvanand-code-chat-api.hf.space/chat', {269                            method: 'POST',270                            headers: {271                                'Content-Type': 'application/json',272                                'Accept': 'text/event-stream'273                            },274                            body: JSON.stringify(data)275                        })276 277                        const reader = response.body.getReader()278 279                        while (true) {280                            const { done, value } = await reader.read()281                            if (done) break282 283                            const chunk = new TextDecoder().decode(value)284                            const lines = chunk.split('\n')285 286                            for (const line of lines) {287                                if (!line.startsWith('data: ')) continue288 289                                try {290                                    const eventData = JSON.parse(line.substring(6))291                                    292                                    switch (eventData.type) {293                                        case 'token':294                                            this.handleToken(eventData)295                                            break296                                        case 'tool_start':297                                            this.handleToolStart(eventData)298                                            break299                                        case 'tool_end':300                                            await this.handleToolEnd(eventData)301                                            break302                                    }303                                } catch (e) {304                                    console.error('Error parsing event:', e)305                                }306                            }307                        }308                    } catch (error) {309                        console.error('Error:', error)310                    }311                },312                handleToken(eventData) {313                    this.currentAssistantMessage += eventData.content314                    this.updateAssistantMessage(this.currentAssistantMessage)315                },316                handleToolStart(eventData) {317                    // Only create a new message if it's a new tool execution318                    if (!this.messages.length || 319                        this.messages[this.messages.length - 1].role !== 'assistant' ||320                        this.messages[this.messages.length - 1].tool_data?.output) {321                        this.messages.push({322                            role: 'assistant',323                            tool_data: {324                                tool: eventData.tool,325                                input: eventData.input326                            }327                        })328                    }329                },330                updateAssistantMessage(content) {331                    const lastMessage = this.messages[this.messages.length - 1]332                    if (lastMessage?.role === 'assistant' && !lastMessage.tool_data) {333                        lastMessage.content = content334                    } else {335                        this.messages.push({336                            role: 'assistant',337                            content: content338                        })339                    }340                }341            },342            beforeUnmount() {343                // Clean up all Plotly charts when component is destroyed344                this.messages.forEach((message, index) => {345                    if (message?.tool_data?.output?.artifacts) {346                        message.tool_data.output.artifacts.forEach((artifact, artifactIndex) => {347                            if (artifact.artifact_type === 'application/vnd.plotly.v1+json') {348                                const elementId = `plot-${index}-${artifactIndex}`349                                Plotly.purge(elementId)350                            }351                        })352                    }353                })354            }355        }).mount('#app')356    </script>357</body>358</html>