mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
Merge pull request #33044 from overleaf/worktree-mg-writefull-setting
Add writefull "AI Assistance" section GitOrigin-RevId: c6d4cb60601c0b808cde96f29f6b79b26f631906
This commit is contained in:
@@ -1071,6 +1071,7 @@ module.exports = {
|
||||
],
|
||||
integrationPanelComponents: [],
|
||||
referenceSearchSetting: [],
|
||||
settingsModalEditorTabSections: [],
|
||||
errorLogsComponents: [],
|
||||
referenceIndices: [],
|
||||
railEntries: [],
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
"ai_assist_in_overleaf_is_included_via_writefull_individual": "",
|
||||
"ai_assist_subscriber_can_now_write_smarter": "",
|
||||
"ai_assist_unavailable_due_to_subscription_type": "",
|
||||
"ai_assistance": "",
|
||||
"ai_can_make_mistakes": "",
|
||||
"ai_features": "",
|
||||
"ai_feedback_please_provide_more_detail": "",
|
||||
@@ -128,6 +129,8 @@
|
||||
"ai_feedback_the_suggestion_didnt_fix_the_error": "",
|
||||
"ai_feedback_the_suggestion_wasnt_the_best_fix_available": "",
|
||||
"ai_feedback_there_was_no_code_fix_suggested": "",
|
||||
"ai_shortcut_on_empty_lines": "",
|
||||
"ai_shortcut_on_text_selection": "",
|
||||
"alignment": "",
|
||||
"all_borders": "",
|
||||
"all_events": "",
|
||||
@@ -1424,6 +1427,8 @@
|
||||
"premium_feature": "",
|
||||
"premium_plan_label": "",
|
||||
"presentation_mode": "",
|
||||
"press_shift_space_for_suggestions": "",
|
||||
"press_space_to_open_the_ai_assistant": "",
|
||||
"preview": "",
|
||||
"preview_editor_tabs": "",
|
||||
"previous_page": "",
|
||||
@@ -1733,6 +1738,7 @@
|
||||
"send_message": "",
|
||||
"send_request": "",
|
||||
"sending": "",
|
||||
"sentence_completion": "",
|
||||
"server_error": "",
|
||||
"server_pro_license_entitlement_line_1": "",
|
||||
"server_pro_license_entitlement_line_2": "",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
import { Nav, NavLink, TabContainer, TabContent } from 'react-bootstrap'
|
||||
import { SettingsEntry } from '../context/settings-modal-context'
|
||||
import { SettingsEntry } from '../context/types'
|
||||
import SettingsTabPane from './settings-tab-pane'
|
||||
import BetaBadgeIcon from '@/shared/components/beta-badge-icon'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TabPane } from 'react-bootstrap'
|
||||
import { SettingsTab } from '../context/settings-modal-context'
|
||||
import { SettingsTab } from '../context/types'
|
||||
import SettingsSection from './settings-section'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import EditorThemeSetting from '@/features/settings/components/appearance-settin
|
||||
import FontSizeSetting from '@/features/settings/components/appearance-settings/font-size-setting'
|
||||
import LineHeightSetting from '@/features/settings/components/appearance-settings/line-height-setting'
|
||||
import FontFamilySetting from '@/features/settings/components/appearance-settings/font-family-setting'
|
||||
import { AvailableUnfilledIcon } from '@/shared/components/material-icon'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import DarkModePdfSetting from '@/features/settings/components/appearance-settings/dark-mode-pdf-setting'
|
||||
|
||||
@@ -32,41 +31,25 @@ import { useProjectSettingsContext } from '@/features/editor-left-menu/context/p
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import ProjectNotificationsSetting from '@/features/settings/components/editor-settings/project-notifications-setting'
|
||||
import getMeta from '@/utils/meta'
|
||||
import type {
|
||||
SettingsEntry,
|
||||
SettingsSection,
|
||||
SettingsSectionHook,
|
||||
} from '@/features/settings/context/types'
|
||||
|
||||
const [referenceSearchSettingModule] = importOverleafModules(
|
||||
'referenceSearchSetting'
|
||||
)
|
||||
const ReferenceSearchSetting = referenceSearchSettingModule?.import.default
|
||||
|
||||
type Setting = {
|
||||
key: string
|
||||
component: React.ReactNode
|
||||
hidden?: boolean
|
||||
}
|
||||
const editorTabExtraSectionHooks: SettingsSectionHook[] = importOverleafModules(
|
||||
'settingsModalEditorTabSections'
|
||||
)
|
||||
.map((m: any) => m?.import?.default)
|
||||
.filter((h: unknown): h is SettingsSectionHook => typeof h === 'function')
|
||||
|
||||
type SettingsSection = {
|
||||
title?: string
|
||||
key: string
|
||||
settings: Setting[]
|
||||
}
|
||||
|
||||
export type SettingsTab = {
|
||||
key: string
|
||||
icon: AvailableUnfilledIcon
|
||||
sections: SettingsSection[]
|
||||
title: string
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
type SettingsLink = {
|
||||
key: string
|
||||
icon: AvailableUnfilledIcon
|
||||
href: string
|
||||
title: string
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type SettingsEntry = SettingsLink | SettingsTab
|
||||
const useSlotSections = (hooks: SettingsSectionHook[]): SettingsSection[] =>
|
||||
hooks.map(hook => hook()).filter((s): s is SettingsSection => s != null)
|
||||
|
||||
type SettingsModalState = {
|
||||
show: boolean
|
||||
@@ -94,6 +77,8 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
const hasEmailNotifications = useFeatureFlag('email-notifications')
|
||||
const hasEditorTabs = useFeatureFlag('editor-tabs')
|
||||
|
||||
const editorTabExtraSections = useSlotSections(editorTabExtraSectionHooks)
|
||||
|
||||
const allSettingsTabs: SettingsEntry[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -168,6 +153,7 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
},
|
||||
],
|
||||
},
|
||||
...editorTabExtraSections,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -276,7 +262,14 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
hidden: !isOverleaf,
|
||||
},
|
||||
],
|
||||
[t, overallTheme, hasEmailNotifications, isOverleaf, hasEditorTabs]
|
||||
[
|
||||
t,
|
||||
hasEditorTabs,
|
||||
overallTheme,
|
||||
hasEmailNotifications,
|
||||
isOverleaf,
|
||||
editorTabExtraSections,
|
||||
]
|
||||
)
|
||||
|
||||
const settingsTabs = useMemo(
|
||||
|
||||
34
services/web/frontend/js/features/settings/context/types.ts
Normal file
34
services/web/frontend/js/features/settings/context/types.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { AvailableUnfilledIcon } from '@/shared/components/material-icon'
|
||||
|
||||
export type Setting = {
|
||||
key: string
|
||||
component: ReactNode
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type SettingsSection = {
|
||||
title?: string
|
||||
key: string
|
||||
settings: Setting[]
|
||||
}
|
||||
|
||||
export type SettingsSectionHook = () => SettingsSection | null
|
||||
|
||||
export type SettingsTab = {
|
||||
key: string
|
||||
icon: AvailableUnfilledIcon
|
||||
sections: SettingsSection[]
|
||||
title: string
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type SettingsLink = {
|
||||
key: string
|
||||
icon: AvailableUnfilledIcon
|
||||
href: string
|
||||
title: string
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type SettingsEntry = SettingsLink | SettingsTab
|
||||
@@ -23,7 +23,7 @@ export type NotificationProps = {
|
||||
isDismissible?: boolean
|
||||
isActionBelowContent?: boolean
|
||||
onDismiss?: () => void
|
||||
title?: string
|
||||
title?: React.ReactNode
|
||||
type: NotificationType
|
||||
id?: string
|
||||
}
|
||||
@@ -114,11 +114,14 @@ function Notification({
|
||||
|
||||
<div className="notification-content-and-cta">
|
||||
<div className="notification-content">
|
||||
{title && (
|
||||
<p>
|
||||
<b>{title}</b>
|
||||
</p>
|
||||
)}
|
||||
{title &&
|
||||
(typeof title === 'string' ? (
|
||||
<p>
|
||||
<b>{title}</b>
|
||||
</p>
|
||||
) : (
|
||||
title
|
||||
))}
|
||||
{content}
|
||||
</div>
|
||||
{action && <div className="notification-cta">{action}</div>}
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"ai_assist_in_overleaf_is_included_via_writefull_individual": "AI Assist in Overleaf is included as part of your Writefull subscription. You can cancel or manage your access to AI Assist in your Writefull subscription settings.",
|
||||
"ai_assist_subscriber_can_now_write_smarter": "AI Assist subscribers can now write smarter, find citations, and generate LaTeX from prompts and images.",
|
||||
"ai_assist_unavailable_due_to_subscription_type": "We’re sorry—it looks like AI Assist isn’t available to you just yet due to your current subscription type.",
|
||||
"ai_assistance": "AI assistance",
|
||||
"ai_assistant": "AI Assistant",
|
||||
"ai_assistant_explanation": "A LaTeX-fluent AI Assistant built into your editor.",
|
||||
"ai_can_make_mistakes": "AI can make mistakes. Review fixes before you apply them.",
|
||||
@@ -163,6 +164,8 @@
|
||||
"ai_feedback_the_suggestion_didnt_fix_the_error": "The suggestion didn’t fix the error",
|
||||
"ai_feedback_the_suggestion_wasnt_the_best_fix_available": "The suggestion wasn’t the best fix available",
|
||||
"ai_feedback_there_was_no_code_fix_suggested": "There was no code fix suggested",
|
||||
"ai_shortcut_on_empty_lines": "AI shortcut on empty lines",
|
||||
"ai_shortcut_on_text_selection": "AI shortcut on text selection",
|
||||
"ai_usage": "AI usage",
|
||||
"ai_usage_explanation": "Control AI usage globally for your organization",
|
||||
"alignment": "Alignment",
|
||||
@@ -1902,6 +1905,8 @@
|
||||
"premium_plan_label": "You’re using <b>Overleaf Premium</b>",
|
||||
"presentation": "Presentation",
|
||||
"presentation_mode": "Presentation mode",
|
||||
"press_shift_space_for_suggestions": "Press Shift+Space for suggestions",
|
||||
"press_space_to_open_the_ai_assistant": "Press Space to open the AI assistant",
|
||||
"preview": "Preview",
|
||||
"preview_editor_tabs": "Preview editor tabs",
|
||||
"previous_24_hours_only": "previous 24 hours only",
|
||||
@@ -2277,6 +2282,7 @@
|
||||
"send_test_email": "Send a test email",
|
||||
"sending": "Sending",
|
||||
"sent": "Sent",
|
||||
"sentence_completion": "Sentence completion",
|
||||
"september": "September",
|
||||
"server_error": "Server Error",
|
||||
"server_pro_license_entitlement_line_1": "<0>__appName__</0> Server Pro license",
|
||||
|
||||
@@ -130,6 +130,98 @@ describe('<SettingsModal />', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a user has Writefull enabled', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-writefullEnabled', true)
|
||||
window.metaAttributesCache.set('ol-showAiFeatures', true)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache.delete('ol-writefullEnabled')
|
||||
window.metaAttributesCache.delete('ol-showAiFeatures')
|
||||
})
|
||||
|
||||
it('shows the AI assistance section in the Editor tab', async function () {
|
||||
render(
|
||||
<EditorProviders
|
||||
rootFolder={[rootFolder as any]}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<SettingsModal />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
selectTab('Editor')
|
||||
await waitFor(() => expect(screen.getByText('AI assistance')).to.exist)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a user does not have Writefull enabled', function () {
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache.delete('ol-writefullEnabled')
|
||||
window.metaAttributesCache.delete('ol-showAiFeatures')
|
||||
window.metaAttributesCache.delete('ol-cannot-use-ai')
|
||||
})
|
||||
|
||||
it('does not show the AI assistance section when ol-writefullEnabled is false', async function () {
|
||||
window.metaAttributesCache.set('ol-writefullEnabled', false)
|
||||
window.metaAttributesCache.set('ol-showAiFeatures', true)
|
||||
render(
|
||||
<EditorProviders
|
||||
rootFolder={[rootFolder as any]}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<SettingsModal />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
selectTab('Editor')
|
||||
await waitFor(
|
||||
() => expect(screen.getByLabelText('Auto-complete')).to.exist
|
||||
)
|
||||
expect(screen.queryByText('AI assistance')).to.be.null
|
||||
})
|
||||
|
||||
it('does not show the AI assistance section when ol-showAiFeatures is false', async function () {
|
||||
window.metaAttributesCache.set('ol-writefullEnabled', true)
|
||||
window.metaAttributesCache.set('ol-showAiFeatures', false)
|
||||
render(
|
||||
<EditorProviders
|
||||
rootFolder={[rootFolder as any]}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<SettingsModal />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
selectTab('Editor')
|
||||
await waitFor(
|
||||
() => expect(screen.getByLabelText('Auto-complete')).to.exist
|
||||
)
|
||||
expect(screen.queryByText('AI assistance')).to.be.null
|
||||
})
|
||||
|
||||
it('does not show the AI assistance section when ol-cannot-use-ai is true', async function () {
|
||||
window.metaAttributesCache.set('ol-writefullEnabled', true)
|
||||
window.metaAttributesCache.set('ol-showAiFeatures', true)
|
||||
window.metaAttributesCache.set('ol-cannot-use-ai', true)
|
||||
render(
|
||||
<EditorProviders
|
||||
rootFolder={[rootFolder as any]}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<SettingsModal />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
selectTab('Editor')
|
||||
await waitFor(
|
||||
() => expect(screen.getByLabelText('Auto-complete')).to.exist
|
||||
)
|
||||
expect(screen.queryByText('AI assistance')).to.be.null
|
||||
})
|
||||
})
|
||||
|
||||
describe('when open=project-notifications query param is present', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-splitTestVariants', {
|
||||
|
||||
Reference in New Issue
Block a user