Skip to content

Commit

Permalink
aichat: code formatting (#21342)
Browse files Browse the repository at this point in the history
  • Loading branch information
nullhook authored Jan 17, 2024
1 parent 76f657d commit b5f4ad0
Show file tree
Hide file tree
Showing 11 changed files with 585 additions and 16 deletions.
3 changes: 3 additions & 0 deletions browser/ui/webui/ai_chat/ai_chat_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ AIChatUI::AIChatUI(content::WebUI* web_ui)
untrusted_source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::FontSrc,
"font-src 'self' data: chrome-untrusted://resources;");

untrusted_source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::TrustedTypes, "trusted-types default;");
}

AIChatUI::~AIChatUI() = default;
Expand Down
1 change: 1 addition & 0 deletions components/ai_chat/resources/page/chat_ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { setIconBasePath } from '@brave/leo/react/icon'
import '$web-components/app.global.scss'
import '@brave/leo/tokens/css/variables.css'

import '$web-common/defaultTrustedTypesPolicy'
import { loadTimeData } from '$web-common/loadTimeData'
import BraveCoreThemeProvider from '$web-common/BraveCoreThemeProvider'
import Main from './components/main'
Expand Down
79 changes: 79 additions & 0 deletions components/ai_chat/resources/page/components/code_block/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* Copyright (c) 2023 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

import * as React from 'react'

import styles from './style.module.scss'
import Button from '@brave/leo/react/button'
import Icon from '@brave/leo/react/icon'
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
import hljsStyle from 'react-syntax-highlighter/dist/esm/styles/hljs/ir-black'
import cpp from 'react-syntax-highlighter/dist/esm/languages/hljs/cpp'
import javascript from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript'
import python from 'react-syntax-highlighter/dist/esm/languages/hljs/python'
import json from 'react-syntax-highlighter/dist/esm/languages/hljs/json'

SyntaxHighlighter.registerLanguage('cpp', cpp)
SyntaxHighlighter.registerLanguage('javascript', javascript)
SyntaxHighlighter.registerLanguage('python', python)
SyntaxHighlighter.registerLanguage('json', json)

interface CodeInlineProps {
code: string
}
interface CodeBlockProps {
code: string
lang: string
}

function Inline(props: CodeInlineProps) {
return (
<span className={styles.container}>
<code>
{props.code}
</code>
</span>
)
}

function Block(props: CodeBlockProps) {
const [hasCopied, setHasCopied] = React.useState(false)

const handleCopy = () => {
navigator.clipboard.writeText(props.code).then(() => {
setHasCopied(true)
setTimeout(() => setHasCopied(false), 1000)
})
}

return (
<div className={styles.container}>
<div className={styles.toolbar}>
<div>{props.lang}</div>
<Button
kind='plain-faint'
onClick={handleCopy}
>
<div slot="icon-before">
<Icon className={styles.icon} name={hasCopied ? 'check-circle-outline' : 'copy'} />
</div>
<div>Copy code</div>
</Button>
</div>
<SyntaxHighlighter
language={props.lang}
style={hljsStyle}
wrapLongLines
>
{props.code}
</SyntaxHighlighter>
</div>
)
}

export default {
Inline,
Block
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2023 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at https://mozilla.org/MPL/2.0/.

.container {
overflow: auto;
background: var(--leo-color-page-background);
border: 1px solid var(--leo-color-divider-subtle);
border-radius: 8px;

pre,
code {
white-space: pre-line;
margin: 0;
}

pre {
padding: var(--leo-spacing-xl);
}

code {
padding: var(--leo-spacing-s);
}
}

.toolbar {
background: var(--leo-color-container-background);
padding: var(--leo-spacing-m) 16px var(--leo-spacing-m) var(--leo-spacing-2xl);
display: flex;
align-items: center;
justify-content: space-between;

leo-button {
max-width: max-content;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ import ContextMenuAssistant from '../context_menu_assistant'
import { getLocale } from '$web-common/locale'
import SiteTitle from '../site_title'

const CodeBlock = React.lazy(async () => ({ default: (await import('../code_block')).default.Block }))
const CodeInline = React.lazy(async () => ({ default: (await import('../code_block')).default.Inline }))

// Capture markdown-style code blocks and inline code.
// It captures:
// 1. Multiline code blocks with optional language specifiers (```lang\n...code...```).
// 2. Inline code segments (`code`).
// 3. Regular text outside of code segments.
const codeFormatRegexp = /```([^\n`]+)?\n?([\s\S]*?)```|`(.*?)`|([^`]+)/gs

const SUGGESTION_STATUS_SHOW_BUTTON: mojom.SuggestionGenerationStatus[] = [
mojom.SuggestionGenerationStatus.CanGenerate,
mojom.SuggestionGenerationStatus.IsGenerating
Expand All @@ -24,6 +34,34 @@ interface ConversationListProps {
onLastElementHeightChange: () => void
}

interface FormattedTextProps {
text: string
}

function FormattedTextRenderer(props: FormattedTextProps): JSX.Element {
const nodes = React.useMemo(() => {
const formattedNodes = Array.from(props.text.matchAll(codeFormatRegexp)).map((match: any) => {
if (match[0].substring(0,3).includes('```')) {
return (<React.Suspense fallback={'...'}>
<CodeBlock lang={match[1]} code={match[2].trim()} />
</React.Suspense>)
} else if (match[0].substring(0,1).includes('`')) {
return (
<React.Suspense fallback={'...'}>
<CodeInline code={match[3]}/>
</React.Suspense>
)
} else {
return match[0]
}
})

return <>{formattedNodes}</>
}, [props.text])

return nodes
}

function ConversationList(props: ConversationListProps) {
const context = React.useContext(DataContext)
const {
Expand Down Expand Up @@ -92,8 +130,10 @@ function ConversationList(props: ConversationListProps) {
<div className={avatarStyles}>
<Icon name={isHuman ? 'user-circle' : 'product-brave-leo'} />
</div>
<div className={styles.message}>
{turn.text}
<div
className={styles.message}
>
{<FormattedTextRenderer text={turn.text} />}
{isLoading && <span className={styles.caret} />}
{showSiteTitle && <div className={styles.siteTitleContainer}><SiteTitle size="default" /></div>}
</div>
Expand Down
12 changes: 11 additions & 1 deletion components/ai_chat/resources/page/stories/components_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,17 @@ const HISTORY = [
text: 'The partial sum formed by the first n + 1 terms of a Taylor series is a polynomial of degree n that is called the nth Taylor polynomial of the function. Taylor polynomials are approximations of a function, which become generally better as n increases.',
characterType: mojom.CharacterType.ASSISTANT,
visibility: mojom.ConversationTurnVisibility.VISIBLE
}
},
{
text: 'Write a hello world program in c++',
characterType: mojom.CharacterType.HUMAN,
visibility: mojom.ConversationTurnVisibility.VISIBLE
},
{
text: "Hello! As a helpful and respectful AI assistant, I'd be happy to assist you with your question. However, I'm a text-based AI and cannot provide code in a specific programming language like C++. Instead, I can offer a brief explanation of how to write a \"hello world\" program in C++.\n\nTo write a \"hello world\" program in C++, you can use the following code:\n\n```c++\n#include <iostream>\n\nint main() {\n std::cout << \"Hello, world!\" << std::endl;\n return 0;\n}\n```\nThis code will print \"Hello, world!\" and uses `iostream` std library. If you have any further questions or need more information, please don't hesitate to ask!",
characterType: mojom.CharacterType.ASSISTANT,
visibility: mojom.ConversationTurnVisibility.VISIBLE
},
]

const MODELS: mojom.Model[] = [
Expand Down
2 changes: 2 additions & 0 deletions components/resources/ai_chat_prompts.grdp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ The current time and date is <ph name="DATE">$1</ph>
Your name is Leo, a helpful, respectful and honest AI assistant created by the company Brave. You will be replying to a user of the Brave browser. Always respond in a neutral tone. Be polite and courteous. Answer concisely in no more than 50-80 words.

Please ensure that your responses are socially unbiased and positive in nature. If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.

Use unicode symbols for formatting where appropriate. Use backticks (`) to wrap inline coding-related words and triple backticks along with language keyword (```language```) to wrap blocks of code or data.
</message>

<message name="IDS_AI_CHAT_LLAMA2_GENERAL_SEED" translateable="false">
Expand Down
Loading

0 comments on commit b5f4ad0

Please sign in to comment.