styled the chat interface

pull/5241/head
STetsing 1 month ago
parent 4c843e9b5f
commit ad70e5ed0a
  1. 11
      apps/remix-ide/src/app/plugins/remixAIPlugin.tsx
  2. 6
      apps/remixdesktop/src/lib/InferenceServerManager.ts
  3. 11
      apps/vyper/src/app/utils/remix-client.tsx
  4. 30
      libs/remix-ai-core/src/agents/codeExplainAgent.ts
  5. 90
      libs/remix-ai-core/src/helpers/streamHandler.ts
  6. 3
      libs/remix-ai-core/src/index.ts
  7. 25
      libs/remix-ai-core/src/inferencers/remote/remoteInference.ts
  8. 3
      libs/remix-ai-core/src/types/types.ts
  9. 94
      libs/remix-ui/remix-ai/src/lib/components/Default.tsx
  10. 2
      libs/remix-ui/remix-ai/src/lib/components/RemixAI.tsx
  11. 94
      libs/remix-ui/remix-ai/src/lib/components/color.css

@ -3,7 +3,7 @@ import { ViewPlugin } from '@remixproject/engine-web'
import { Plugin } from '@remixproject/engine'; import { Plugin } from '@remixproject/engine';
import { RemixAITab, ChatApi } from '@remix-ui/remix-ai' import { RemixAITab, ChatApi } from '@remix-ui/remix-ai'
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, HandleStreamResponse } from '@remix/remix-ai-core'; import { ICompletions, IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, CodeExplainAgent} from '@remix/remix-ai-core';
type chatRequestBufferT<T> = { type chatRequestBufferT<T> = {
[key in keyof T]: T[key] [key in keyof T]: T[key]
@ -33,11 +33,12 @@ export class RemixAIPlugin extends ViewPlugin {
remoteInferencer:RemoteInferencer = null remoteInferencer:RemoteInferencer = null
isInferencing: boolean = false isInferencing: boolean = false
chatRequestBuffer: chatRequestBufferT<any> = null chatRequestBuffer: chatRequestBufferT<any> = null
agent: CodeExplainAgent
constructor(inDesktop:boolean) { constructor(inDesktop:boolean) {
super(profile) super(profile)
this.isOnDesktop = inDesktop this.isOnDesktop = inDesktop
this.agent = new CodeExplainAgent(this)
// user machine dont use ressource for remote inferencing // user machine dont use ressource for remote inferencing
} }
@ -113,11 +114,12 @@ export class RemixAIPlugin extends ViewPlugin {
return return
} }
const newPrompt = await this.agent.chatCommand(prompt)
let result let result
if (this.isOnDesktop) { if (this.isOnDesktop) {
result = await this.call(this.remixDesktopPluginName, 'solidity_answer', prompt) result = await this.call(this.remixDesktopPluginName, 'solidity_answer', newPrompt)
} else { } else {
result = await this.remoteInferencer.solidity_answer(prompt) result = await this.remoteInferencer.solidity_answer(newPrompt)
} }
if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result }) if (result && params.terminal_output) this.call('terminal', 'log', { type: 'aitypewriterwarning', value: result })
return result return result
@ -171,7 +173,6 @@ export class RemixAIPlugin extends ViewPlugin {
prompt: prompt, prompt: prompt,
context: context context: context
} }
console.log('pipe message', pipeMessage)
if (pipeMessage) ChatApi.composer.send(pipeMessage) if (pipeMessage) ChatApi.composer.send(pipeMessage)
else { else {
if (fn === "code_explaining") ChatApi.composer.send("Explain the current code") if (fn === "code_explaining") ChatApi.composer.send("Explain the current code")

@ -404,8 +404,12 @@ export class InferenceManager implements ICompletions {
} }
, responseType: 'stream' }); , responseType: 'stream' });
const userPrompt = payload[Object.keys(payload)[0]] const userPrompt = payload.prompt
let resultText = "" let resultText = ""
if (payload.return_stream_response) {
return response
}
response.data.on('data', (chunk: Buffer) => { response.data.on('data', (chunk: Buffer) => {
try { try {
const parsedData = JSON.parse(chunk.toString()); const parsedData = JSON.parse(chunk.toString());

@ -8,6 +8,7 @@ import EventEmitter from 'events'
import { Plugin } from "@remixproject/engine"; import { Plugin } from "@remixproject/engine";
import { CustomRemixApi } from '@remix-api' import { CustomRemixApi } from '@remix-api'
export type VyperComplierAddress = 'https://vyper2.remixproject.org/' | 'http://localhost:8000/' export type VyperComplierAddress = 'https://vyper2.remixproject.org/' | 'http://localhost:8000/'
export class RemixClient extends PluginClient<any, CustomRemixApi> { export class RemixClient extends PluginClient<any, CustomRemixApi> {
private client = createClient<Api, Readonly<RemixApi>>(this) private client = createClient<Api, Readonly<RemixApi>>(this)
@ -68,12 +69,10 @@ export class RemixClient extends PluginClient<any, CustomRemixApi> {
} }
try { try {
// TODO: remove! no formatting required since already handled on server // TODO: remove! no formatting required since already handled on server
const formattedMessage = ` const file = await this.client.call('fileManager', 'getCurrentFile')
${message} const content = await this.client.call('fileManager', 'readFile', file)
can you explain why this error occurred and how to fix it? const messageAI = `Vyper code: ${content}\n error message: ${message}\n explain why the error occurred and how to fix it.`
` await this.client.call('remixAI' as any, 'chatPipe', 'error_explaining', messageAI)
// await this.client.call('remixAI' as any, 'error_explaining', message)
await this.client.call('remixAI' as any, 'chatPipe', 'error_explaining', message)
} catch (err) { } catch (err) {
console.error('unable to askGpt') console.error('unable to askGpt')
console.error(err) console.error(err)

@ -1,29 +1,45 @@
// interactive code explaining and highlight security vunerabilities // interactive code explaining and highlight security vunerabilities
import * as fs from 'fs'; import * as fs from 'fs';
class CodeExplainAgent { export class CodeExplainAgent {
private codebase: string[]; // list of code base file private codebase: string[]; // list of code base file
public currentFile: string; public currentFile: string;
plugin
constructor(codebasePath: string) { constructor(props) {
this.plugin = props
// git or fs // git or fs
this.codebase = this.loadCodebase(codebasePath); const codebase = this.loadCodebase("codebasePath");
} }
private loadCodebase(path: string): string[] { private loadCodebase(path: string): string[] {
const files = fs.readdirSync(path); return []
return files
.filter(file => file.endsWith('.ts'))
.flatMap(file => fs.readFileSync(`${path}/${file}`, 'utf-8').split('\n'));
} }
public update(currentFile, lineNumber){ public update(currentFile, lineNumber){
} }
async chatCommand(prompt:string){
// change this function with indexer or related
try{
if (prompt.includes('Explain briefly the current file')){
const file = await this.plugin.call('fileManager', 'getCurrentFile')
const content = `Explain this code:\n ${await this.plugin.call('fileManager', 'readFile', file)}`
return content
} else return prompt
} catch {
console.log('There is No file selected')
return 'There is No file selected'
}
}
public getExplanations(currentLine: string, numSuggestions: number = 3): string[] { public getExplanations(currentLine: string, numSuggestions: number = 3): string[] {
// process the code base explaining the current file and highlight some details // process the code base explaining the current file and highlight some details
const suggestions: string[] = []; const suggestions: string[] = [];
return suggestions; return suggestions;
} }
} }
// Handle file changed (significantly)

@ -1,61 +1,61 @@
import { ChatHistory } from '../prompts/chat'; import { ChatHistory } from '../prompts/chat';
import { JsonStreamParser} from '../types/types' import { JsonStreamParser } from '../types/types'
export const HandleSimpleResponse = async (response, export const HandleSimpleResponse = async (response,
cb?: (streamText: string) => void) => { cb?: (streamText: string) => void) => {
let resultText = ''
const parser = new JsonStreamParser();
const chunk = parser.safeJsonParse<{ generatedText: string; isGenerating: boolean }>(response);
for (const parsedData of chunk) {
if (parsedData.isGenerating) {
resultText += parsedData.generatedText
cb(parsedData.generatedText)
} else {
resultText += parsedData.generatedText
cb(parsedData.generatedText)
}
}
}
export const HandleStreamResponse = async (streamResponse,
cb?: (streamText: string) => void,
done_cb?: (result: string) => void) => {
try {
let resultText = '' let resultText = ''
const parser = new JsonStreamParser(); const parser = new JsonStreamParser();
const reader = streamResponse.body!.getReader();
const decoder = new TextDecoder();
const chunk = parser.safeJsonParse<{ generatedText: string; isGenerating: boolean }>(response); while (true) {
for (const parsedData of chunk) { const { done, value } = await reader.read();
if (parsedData.isGenerating) { if (done) break;
try {
const chunk = parser.safeJsonParse<{ generatedText: string; isGenerating: boolean }>(decoder.decode(value, { stream: true }));
for (const parsedData of chunk) {
if (parsedData.isGenerating) {
resultText += parsedData.generatedText resultText += parsedData.generatedText
cb(parsedData.generatedText) cb(parsedData.generatedText)
} else { } else {
resultText += parsedData.generatedText resultText += parsedData.generatedText
cb(parsedData.generatedText) cb(parsedData.generatedText)
}
} }
} }
} catch (error) {
export const HandleStreamResponse = async (streamResponse,
cb?: (streamText: string) => void,
done_cb?: (result: string) => void) => {
try {
let resultText = ''
const parser = new JsonStreamParser();
const reader = streamResponse.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
try {
const chunk = parser.safeJsonParse<{ generatedText: string; isGenerating: boolean }>(decoder.decode(value, { stream: true }));
for (const parsedData of chunk) {
if (parsedData.isGenerating) {
resultText += parsedData.generatedText
cb(parsedData.generatedText)
} else {
resultText += parsedData.generatedText
cb(parsedData.generatedText)
}
}
}
catch (error) {
console.error('Error parsing JSON:', error);
}
}
if (done_cb) {
done_cb(resultText)
}
}
catch (error) {
console.error('Error parsing JSON:', error); console.error('Error parsing JSON:', error);
}
}
if (done_cb) {
done_cb(resultText)
} }
}
catch (error) {
console.error('Error parsing JSON:', error);
}
} }
export const UpdtateChatHistory = (userPromptprompt: string, AIAnswer: string) => { export const UpdtateChatHistory = (userPromptprompt: string, AIAnswer: string) => {
ChatHistory.pushHistory(userPromptprompt, AIAnswer) ChatHistory.pushHistory(userPromptprompt, AIAnswer)
} }

@ -20,4 +20,5 @@ export {
} }
export * from './types/types' export * from './types/types'
export * from './helpers/streamHandler' export * from './helpers/streamHandler'
export * from './agents/codeExplainAgent'

@ -17,20 +17,21 @@ export class RemoteInferencer implements ICompletions {
model_op = RemoteBackendOPModel.CODELLAMA // default model operation change this to llama if necessary model_op = RemoteBackendOPModel.CODELLAMA // default model operation change this to llama if necessary
event: EventEmitter event: EventEmitter
test_env=true test_env=true
test_url="http://solcodertest.org/"
constructor(apiUrl?:string, completionUrl?:string) { constructor(apiUrl?:string, completionUrl?:string) {
this.api_url = apiUrl!==undefined ? apiUrl: this.test_env? "http://127.0.0.1:7861/" : "https://solcoder.remixproject.org" this.api_url = apiUrl!==undefined ? apiUrl: this.test_env? this.test_url : "https://solcoder.remixproject.org"
this.completion_url = completionUrl!==undefined ? completionUrl : this.test_env? "http://127.0.0.1:7861/" : "https://completion.remixproject.org" this.completion_url = completionUrl!==undefined ? completionUrl : this.test_env? this.test_url : "https://completion.remixproject.org"
this.event = new EventEmitter() this.event = new EventEmitter()
} }
private async _makeRequest(endpoint, payload, rType:AIRequestType){ private async _makeRequest(payload, rType:AIRequestType){
this.event.emit("onInference") this.event.emit("onInference")
const requestURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url const requestURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url
try { try {
const options = { headers: { 'Content-Type': 'application/json', } } const options = { headers: { 'Content-Type': 'application/json', } }
const result = await axios.post(`${requestURL}/${endpoint}`, payload, options) const result = await axios.post(`${requestURL}`, payload, options)
switch (rType) { switch (rType) {
case AIRequestType.COMPLETION: case AIRequestType.COMPLETION:
@ -63,11 +64,10 @@ export class RemoteInferencer implements ICompletions {
try { try {
this.event.emit('onInference') this.event.emit('onInference')
const requestURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url const requestURL = rType === AIRequestType.COMPLETION ? this.completion_url : this.api_url
const response = await fetch(`${requestURL}/${endpoint}`, { const response = await fetch(requestURL, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
"Accept": "text/event-stream",
}, },
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
@ -118,20 +118,20 @@ export class RemoteInferencer implements ICompletions {
async code_completion(prompt, options:IParams=CompletionParams): Promise<any> { async code_completion(prompt, options:IParams=CompletionParams): Promise<any> {
const payload = { prompt, "endpoint":"code_completion", ...options } const payload = { prompt, "endpoint":"code_completion", ...options }
return this._makeRequest(payload.endpoint, payload, AIRequestType.COMPLETION) return this._makeRequest(payload, AIRequestType.COMPLETION)
} }
async code_insertion(msg_pfx, msg_sfx, options:IParams=InsertionParams): Promise<any> { async code_insertion(msg_pfx, msg_sfx, options:IParams=InsertionParams): Promise<any> {
// const payload = { "data":[msg_pfx, "code_insertion", msg_sfx, 1024, 0.5, 0.92, 50]} // const payload = { "data":[msg_pfx, "code_insertion", msg_sfx, 1024, 0.5, 0.92, 50]}
const payload = {"endpoint":"code_insertion", msg_pfx, msg_sfx, ...options, prompt: '' } const payload = {"endpoint":"code_insertion", msg_pfx, msg_sfx, ...options, prompt: '' }
return this._makeRequest(payload.endpoint, payload, AIRequestType.COMPLETION) return this._makeRequest(payload, AIRequestType.COMPLETION)
} }
async code_generation(prompt, options:IParams=GenerationParams): Promise<any> { async code_generation(prompt, options:IParams=GenerationParams): Promise<any> {
// const payload = { "data":[prompt, "code_completion", "", false,1000,0.9,0.92,50]} // const payload = { "data":[prompt, "code_completion", "", false,1000,0.9,0.92,50]}
const payload = { prompt, "endpoint":"code_completion", ...options } const payload = { prompt, "endpoint":"code_completion", ...options }
if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.COMPLETION) if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.COMPLETION)
else return this._makeRequest(payload.endpoint, payload, AIRequestType.COMPLETION) else return this._makeRequest(payload, AIRequestType.COMPLETION)
} }
async solidity_answer(prompt, options:IParams=GenerationParams): Promise<any> { async solidity_answer(prompt, options:IParams=GenerationParams): Promise<any> {
@ -139,20 +139,19 @@ export class RemoteInferencer implements ICompletions {
// const payload = { "data":[main_prompt, "solidity_answer", false,2000,0.9,0.8,50]} // const payload = { "data":[main_prompt, "solidity_answer", false,2000,0.9,0.8,50]}
const payload = { 'prompt': main_prompt, "endpoint":"solidity_answer", ...options } const payload = { 'prompt': main_prompt, "endpoint":"solidity_answer", ...options }
if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.GENERAL) if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.GENERAL)
else return this._makeRequest(payload.endpoint, payload, AIRequestType.GENERAL) else return this._makeRequest(payload, AIRequestType.GENERAL)
} }
async code_explaining(prompt, context:string="", options:IParams=GenerationParams): Promise<any> { async code_explaining(prompt, context:string="", options:IParams=GenerationParams): Promise<any> {
// const payload = { "data":[prompt, "code_explaining", false,2000,0.9,0.8,50, context]} // const payload = { "data":[prompt, "code_explaining", false,2000,0.9,0.8,50, context]}
const payload = { prompt, "endpoint":"code_explaining", context, ...options } const payload = { prompt, "endpoint":"code_explaining", context, ...options }
if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.GENERAL) if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload, AIRequestType.GENERAL)
else return this._makeRequest(payload.endpoint, payload, AIRequestType.GENERAL) else return this._makeRequest(payload, AIRequestType.GENERAL)
} }
async error_explaining(prompt, options:IParams=GenerationParams): Promise<any> { async error_explaining(prompt, options:IParams=GenerationParams): Promise<any> {
const payload = { prompt, "endpoint":"error_explaining", ...options } const payload = { prompt, "endpoint":"error_explaining", ...options }
console.log("payload: ", payload)
if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload , AIRequestType.GENERAL) if (options.stream_result) return this._streamInferenceRequest(payload.endpoint, payload , AIRequestType.GENERAL)
else return this._makeRequest(payload.endpoint, payload, AIRequestType.GENERAL) else return this._makeRequest(payload, AIRequestType.GENERAL)
} }
} }

@ -122,4 +122,7 @@ export class JsonStreamParser {
return results; return results;
} }
safeJsonParseSingle<T>(chunk: string): T[] | null {
return JSON.parse(this.buffer);
}
} }

@ -4,10 +4,13 @@ import { DefaultModels, GenerationParams, ChatHistory, HandleStreamResponse, Han
import { ConversationStarter, StreamSend, StreamingAdapterObserver, useAiChatApi } from '@nlux/react'; import { ConversationStarter, StreamSend, StreamingAdapterObserver, useAiChatApi } from '@nlux/react';
import axios from 'axios'; import axios from 'axios';
import { AiChat, useAsStreamAdapter, ChatItem, AiChatUI} from '@nlux/react'; import { AiChat, useAsStreamAdapter, ChatItem, AiChatUI} from '@nlux/react';
import '@nlux/themes/nova.css';
import { JsonStreamParser } from '@remix/remix-ai-core'; import { JsonStreamParser } from '@remix/remix-ai-core';
import { user, assistantAvatar } from './personas'; import { user, assistantAvatar } from './personas';
import {highlighter} from '@nlux/highlighter' import {highlighter} from '@nlux/highlighter'
import './color.css'
import '@nlux/themes/unstyled.css';
// import '@nlux/themes'
import { result } from 'lodash';
const demoProxyServerUrl = 'https://solcoder.remixproject.org'; const demoProxyServerUrl = 'https://solcoder.remixproject.org';
@ -44,8 +47,8 @@ export const Default = (props) => {
}; };
ChatApi = useAiChatApi(); ChatApi = useAiChatApi();
const conversationStarters: ConversationStarter[] = [ const conversationStarters: ConversationStarter[] = [
{prompt: 'Explain what is a solidity contract!', icon: <span></span>}, {prompt: 'Explain briefly the current file in Editor', icon: <span></span>},
{prompt: 'Explain the current file in Editor'}] {prompt: 'Explain what is a solidity contract!'}]
// Define initial messages // Define initial messages
const initialMessages: ChatItem[] = [ const initialMessages: ChatItem[] = [
@ -71,7 +74,7 @@ export const Default = (props) => {
}} }}
//initialConversation={initialMessages} //initialConversation={initialMessages}
conversationOptions={{ layout: 'bubbles', conversationStarters }} conversationOptions={{ layout: 'bubbles', conversationStarters }}
displayOptions={{ colorScheme: "auto", themeId: "nova" }} displayOptions={{ colorScheme: "auto", themeId: "remix_ai_theme" }}
composerOptions={{ placeholder: "Type your query", composerOptions={{ placeholder: "Type your query",
submitShortcut: 'Enter', submitShortcut: 'Enter',
hideStopButton: false, hideStopButton: false,
@ -83,85 +86,4 @@ export const Default = (props) => {
}} }}
/> />
); );
}; };
// export const Default = (props) => {
// const [searchText, setSearchText] = useState('');
// const [resultText, setResultText] = useState('');
// const pluginName = 'remixAI'
// const appendText = (newText) => {
// setResultText(resultText => resultText + newText);
// }
// useEffect(() => {
// const handleResultReady = async (e) => {
// appendText(e);
// };
// if (props.plugin.isOnDesktop ) {
// props.plugin.on(props.plugin.remixDesktopPluginName, 'onStreamResult', (value) => {
// handleResultReady(value);
// })
// }
// }, [])
// return (
// <div>
// <div className="remix_ai_plugin_search_container">
// <input
// type="text"
// className="remix_ai_plugin_search-input"
// placeholder="Search..."
// value={searchText}
// onChange={() => console.log('searchText not implememted')}
// ></input>
// <button
// className="remix_ai_plugin_search_button text-ai pl-2 pr-0 py-0 d-flex"
// onClick={() => console.log('searchText not implememted')}
// >
// <i
// className="fa-solid fa-arrow-right"
// style={{ color: 'black' }}
// ></i>
// <span className="position-relative text-ai text-sm pl-1"
// style={{ fontSize: "x-small", alignSelf: "end" }}>Search</span>
// </button>
// <button className="remix_ai_plugin_download_button text-ai pl-2 pr-0 py-0 d-flex"
// onClick={async () => {
// if (props.plugin.isOnDesktop ) {
// await props.plugin.call(pluginName, 'downloadModel', DefaultModels()[3]);
// }
// }}
// > Download Model </button>
// </div>
// <div className="remix_ai_plugin_find_container_internal">
// <textarea
// className="remix_ai_plugin_search_result_textbox"
// rows={10}
// cols={50}
// placeholder="Results..."
// onChange={(e) => {
// console.log('resultText changed', e.target.value)
// setResultText(e.target.value)}
// }
// value={resultText}
// readOnly
// />
// <button className="remix_ai_plugin_download_button text-ai pl-2 pr-0 py-0 d-flex"
// onClick={async () => {
// props.plugin.call("remixAI", 'initialize', DefaultModels()[1], DefaultModels()[3]);
// }}
// > Init Model </button>
// </div>
// <div className="remix_ai_plugin_find-part">
// <a href="#" className="remix_ai_plugin_search_result_item_title">/fix the problems in my code</a>
// <a href="#" className="remix_ai_plugin_search_result_item_title">/tests add unit tests for my code</a>
// <a href="#" className="remix_ai_plugin_search_result_item_title">/explain how the selected code works</a>
// </div>
// </div>
// );
// }

@ -7,7 +7,7 @@ export const RemixAITab = (props) => {
const plugin = props.plugin const plugin = props.plugin
return ( return (
<> <>
<div id="remixAITab pr-4 px-2 pb-4"> <div id="remixAITab" className="px-2 pb-4">
<Default plugin={plugin}></Default> <Default plugin={plugin}></Default>
</div> </div>
</> </>

@ -0,0 +1,94 @@
.nlux-theme-remix_ai_theme[data-color-scheme='light'] {
--nlux-ChatRoom--BackgroundColor: var(--text-background);
}
.nlux-theme-remix_ai_theme[data-color-scheme='dark'] {
--nlux-ChatRoom--BackgroundColor: var(--text-background);
}
.nlux-theme-remix_ai_theme {
/* Override top-level chat room colors */
--nlux-ChatRoom--BorderColor: #24233d;
--nlux-ChatRoom-Divider--Color: var(--light);
/* --nlux-ChatRoom-Divider--BorderWidth:2px; */
--nlux-ChatRoom--TextColor: var(--text);
/* Override message bubble colors */
--nlux-AiMessage--BackgroundColor: var(--light);
--nlux-HumanMessage--BackgroundColor: var(--text-background);
/* Override border width */
--nlux-ChatRoom--BorderWidth: 0;
--nlux-SubmitButton--BorderWidth: 0;
--nlux-ChatItem-Avatar--BorderWidth: 0;
--nlux-ChatItem-Message-BubbleLayout--BorderWidth: 0;
--nlux-ConversationStarter--BorderWidth: 1;
/* Override border radius */
--nlux-ChatRoom--BorderRadius: 5px;
--nlux-SubmitButton--BorderRadius: 0 10px 10px 0;
--nlux-SubmitButton--Width: 73px;
--nlux-ChatItem-Avatar--BorderRadius: 5px;
--nlux-ChatItem-Message-BubbleLayout--BorderRadius: 5px;
--nlux-ConversationStarter--BorderRadius: 5px;
--nlux-PromptInput-Focus-Outline--Width: 10px;
--nlux-PromptInput-Max-Height: 50px;
--nlux-PromptInput--BorderWidth: 0;
.nlux-comp-composer > textarea {padding: 8px;}
--nlux-PromptInput--BorderRadius: 10px 0 0 10px;
--nlux-PromptInput-Height: 50px;
/* Override input colors */
--nlux-PromptInput--BackgroundColor: var(--light);
--nlux-PromptInput-Active--BackgroundColor: var(--light);
--nlux-PromptInput-Disabled--BackgroundColor: var(--dark);
/* Gap between submit button and input */
--nlux-Composer--Gap: 0;
/* Override submit button colors */
--nlux-SubmitButton--BackgroundColor: var(--primary);
--nlux-SubmitButton-Active--BackgroundColor:var(--primary);
--nlux-SubmitButton-Disabled--BackgroundColor: var(--dark);
--nlux-SubmitButton-Active--TextColor: var(--text);
--nlux-SubmitButton-Disabled--TextColor: var(--text);
/** Inline code in markdown */
--nlux-InlineCode--BorderRadius: 6px;
--nlux-InlineCode--BorderWidth: 0.5px;
--nlux-InlineCode--Padding: 0 2px;
--nlux-InlineCode--FontSize: 14px;
/*code block */
--nlux-CodeBlock-CopyButton--BackgroundColor: var(--bg-text);
--nlux-CodeBlock-CopyButton--TextColor: var(--text);
/*codeblock*/
/*--nlux-CodeBlock--BackgroundColor: var(--body-bg);*/
--nlux-CodeBlock--BackgroundColor: var(--bg-text);
--nlux-CodeBlock--BorderColor: var(--secondary);
--nlux-CodeBlock--Padding: 20px;
--nlux-CodeBlock--TextColor: var(--text);
--nlux-CodeBlock--FontSize: 14px;
/* Conversation starter colors */
--nlux-ConversationStarter--BackgroundColor: var(--light);
--nlux-copy-icon: url('data:image/svg+xml,\
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">\
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1.25H10.9436C9.10583 1.24998 7.65019 1.24997 6.51098 1.40314C5.33856 1.56076 4.38961 1.89288 3.64124 2.64124C2.89288 3.38961 2.56076 4.33856 2.40314 5.51098C2.24997 6.65019 2.24998 8.10582 2.25 9.94357V16C2.25 17.8722 3.62205 19.424 5.41551 19.7047C5.55348 20.4687 5.81753 21.1208 6.34835 21.6517C6.95027 22.2536 7.70814 22.5125 8.60825 22.6335C9.47522 22.75 10.5775 22.75 11.9451 22.75H15.0549C16.4225 22.75 17.5248 22.75 18.3918 22.6335C19.2919 22.5125 20.0497 22.2536 20.6517 21.6517C21.2536 21.0497 21.5125 20.2919 21.6335 19.3918C21.75 18.5248 21.75 17.4225 21.75 16.0549V10.9451C21.75 9.57754 21.75 8.47522 21.6335 7.60825C21.5125 6.70814 21.2536 5.95027 20.6517 5.34835C20.1208 4.81753 19.4687 4.55348 18.7047 4.41551C18.424 2.62205 16.8722 1.25 15 1.25ZM17.1293 4.27117C16.8265 3.38623 15.9876 2.75 15 2.75H11C9.09318 2.75 7.73851 2.75159 6.71085 2.88976C5.70476 3.02502 5.12511 3.27869 4.7019 3.7019C4.27869 4.12511 4.02502 4.70476 3.88976 5.71085C3.75159 6.73851 3.75 8.09318 3.75 10V16C3.75 16.9876 4.38624 17.8265 5.27117 18.1293C5.24998 17.5194 5.24999 16.8297 5.25 16.0549V10.9451C5.24998 9.57754 5.24996 8.47522 5.36652 7.60825C5.48754 6.70814 5.74643 5.95027 6.34835 5.34835C6.95027 4.74643 7.70814 4.48754 8.60825 4.36652C9.47522 4.24996 10.5775 4.24998 11.9451 4.25H15.0549C15.8297 4.24999 16.5194 4.24998 17.1293 4.27117ZM7.40901 6.40901C7.68577 6.13225 8.07435 5.9518 8.80812 5.85315C9.56347 5.75159 10.5646 5.75 12 5.75H15C16.4354 5.75 17.4365 5.75159 18.1919 5.85315C18.9257 5.9518 19.3142 6.13225 19.591 6.40901C19.8678 6.68577 20.0482 7.07435 20.1469 7.80812C20.2484 8.56347 20.25 9.56458 20.25 11V16C20.25 17.4354 20.2484 18.4365 20.1469 19.1919C20.0482 19.9257 19.8678 20.3142 19.591 20.591C19.3142 20.8678 18.9257 21.0482 18.1919 21.1469C17.4365 21.2484 16.4354 21.25 15 21.25H12C10.5646 21.25 9.56347 21.2484 8.80812 21.1469C8.07435 21.0482 7.68577 20.8678 7.40901 20.591C7.13225 20.3142 6.9518 19.9257 6.85315 19.1919C6.75159 18.4365 6.75 17.4354 6.75 16V11C6.75 9.56458 6.75159 8.56347 6.85315 7.80812C6.9518 7.07435 7.13225 6.68577 7.40901 6.40901Z" fill="currentColor"/>\
</svg>\
');
/* Override icon for the send button */
--nlux-send-icon: url('data:image/svg+xml,\
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" \
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\
<circle cx="12" cy="12" r="10"/>\
<path d="M16 12l-4-4-4 4M12 16V9"/>\
</svg>\
');
}
Loading…
Cancel
Save