Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aichat: code formatting #21342

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;");
diracdeltas marked this conversation as resolved.
Show resolved Hide resolved
}

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Well, now that this is so simple, you could more simply just wrap this function in React.memo

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