Task: Format content

Format content

Fix poor page formatting resulting from a low-quality website update.

Format content

Currently, many pages on the website have poor formatting as a result of an unsuccessful website update.

You need to go through the problematic pages and bring the content to a neat, uniform appearance.

What to do

  • identify pages with broken or messy formatting;
  • fix the structure of headings, lists, paragraphs, and other blocks;
  • remove visual artifacts and traces of incorrect migration/update;
  • bring the formatting to a single style across the website;
  • check the final display of the pages after edits.

Result

The content on the pages should look neat, consistent, and without obvious traces of a poor-quality website update.

Ворклоги

Progress has been recorded on the automatic restoration of old content formatting after migration from draft-editor.

A resolver has been written that takes old content variants (contentText and content) and, via an AI agent, reassembles the publication into Markdown while preserving the original words, meaning, and style.

import { Resource } from '@prisma/client'
import { PrismaContext } from 'server/context/interfaces'
import {
  LLMChatMessageRole,
  LlmModel,
  LlmProvider,
} from 'server/llm/client/interfaces'
import { llmChatCompletionResolver } from 'server/schema/types/LLM/resolvers/chatCompletion'

type ProcessConceptsArgs = {
  ctx: PrismaContext
  resource: Resource
}

export async function processOldContent({
  ctx,
  resource,
}: ProcessConceptsArgs): Promise<string | null | undefined> {
  const systemPrompt = `# You are a rewriter.

You will be given different variants of text for the same publication. This can be plain text or technical text with various technical tags, etc. Your task is to completely reassemble the article, preserving the words, meaning, and style, but format everything in Markdown format.

Code listings may also be provided; they need to be inserted into a code block with the language specified. For example:

\`\`\`js
const foo = 0
\`\`\`

List of all supported languages: 
'': 'Plain Text',
text: 'Plain Text',
js: 'JavaScript',
ts: 'TypeScript',
tsx: 'TypeScript (React)',
jsx: 'JavaScript (React)',
css: 'CSS',
html: 'HTML',
json: 'JSON',
bash: 'Bash',
shell: 'Shell',
sql: 'SQL',
gql: 'GraphQL',
python: 'Python',
markdown: 'Markdown',
django: 'Jinja/Django',
env: 'Environment',
yaml: 'YAML',

If none of them fit, do not explicitly specify the language used.


## !!! Very Important !!! 

It is important to preserve the meaning, words, and style. Your task is to format the article, not to rewrite it in your own words. You must not assume, invent, or change anything. If there is ambiguity somewhere, that ambiguity must remain—your only task is to format the article.

You must return only the formatted article in Markdown format, without any technical accompanying code, text, your comments, etc. Just the article itself. 


## Formatting of the Final Publication

Valid Markdown with html support (though Markdown formatting is preferred)

It is important to take into account that spaces are missing in the text in many places. It is very important to recognize this and insert spaces correctly.

Images are inserted as images with a preview. Links as links.

If there are links referencing the community.modx-cms.ru domain, insert them simply as text rather than a link.
For other domains, insert links as they are. 

If there are images from external file hosting services like joxi, etc., do not insert them; they are outdated. Be sure to insert local images where they are specified.
`

  const {
    content,
    contentText,

    /**
     * we do not take contentV2 because it was generated from content and contentText
     */
    // contentV2
  } = resource

  const peaces = [contentText, content]
    .map((n) =>
      n === null ? null : typeof n === 'string' ? n.trim() : JSON.stringify(n),
    )
    .filter((n) => !!n)

  if (!peaces.length) {
    return ''
  }

  const userPrompt = `## Here is the source data for the article. Rewrite this into the final article in Markdown format, preserving the words, meaning, and style, and return only it to me, without any additional technical text and your comments

---

${peaces.join(`

---

`)}
  
`

  const chatResponse = await llmChatCompletionResolver(
    null,
    {
      input: {
        provider: LlmProvider.OpenRouter,
        messages: [
          {
            role: LLMChatMessageRole.system,
            content: systemPrompt,
          },
          {
            role: LLMChatMessageRole.user,
            content: userPrompt,
          },
        ],
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        model: LlmModel.GEMINI_3_5_FLASH_LITE as any,
      },
    },
    ctx,
  )

  const responseContent = chatResponse.choices?.[0]?.message?.content

  return responseContent
}

Execution statistics:

  • 100 publications — about 5 minutes, budget ~$0.30.
  • 1296 resources — about 43 minutes, budget $2.53.

Overall, the processing worked out fine: the text became noticeably more readable. Prior to this, after migrating from draft-editor, a significant portion of publications had essentially turned into a solid block of text without the original formatting.

At the same time, automated processing does not yield a completely clean result and leaves individual artifacts. For example, the following result can occur:

В create- процессоре она есть, а в update- процессоре ее нет. Уточню, что это ключ массива данных, передаваемых в массив 
s
c
r
i
p
t
P
r
o
p
e
r
t
i
e
s
,
доступныйвнавешиваемыхплагинах
.
Приэтомониещеисоветуютэтотключнеиспользовать
,
ииспользуйтетипаключ
scriptProperties,доступныйвнавешиваемыхплагинах.Приэтомониещеисоветуютэтотключнеиспользовать,ииспользуйтетипаключthis->objectType.

The original HTML of this sentence:

<p>В create- процессоре она есть, а в update- процессоре ее нет. Уточню, что это ключ массива данных, передаваемых в массив <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mi>c</mi><mi>r</mi><mi>i</mi><mi>p</mi><mi>t</mi><mi>P</mi><mi>r</mi><mi>o</mi><mi>p</mi><mi>e</mi><mi>r</mi><mi>t</mi><mi>i</mi><mi>e</mi><mi>s</mi><mo separator="true">,</mo><mtext>доступныйвнавешиваемыхплагинах</mtext><mi mathvariant="normal">.</mi><mtext>Приэтомониещеисоветуютэтотключнеиспользовать</mtext><mo separator="true">,</mo><mtext>ииспользуйтетипаключ</mtext></mrow><annotation encoding="application/x-tex">scriptProperties, доступный в навешиваемых плагинах. При этом они еще и советуют этот ключ не использовать, и используйте типа ключ </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em"></span><span class="mord mathnormal" style="margin-right:0.0278em">scr</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.1389em">ptP</span><span class="mord mathnormal" style="margin-right:0.0278em">r</span><span class="mord mathnormal">o</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.0278em">er</span><span class="mord mathnormal">t</span><span class="mord mathnormal">i</span><span class="mord mathnormal">es</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em"></span><span class="mord cyrillic_fallback">доступныйвнавешиваемыхплагинах</span><span class="mord">.</span><span class="mord cyrillic_fallback">Приэтомониещеисоветуютэтотключнеиспользовать</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em"></span><span class="mord cyrillic_fallback">ииспользуйтетипаключ</span></span></span></span>this-&gt;objectType.</p>

Here, the problem was already embedded in the old HTML: plain text ended up wrapped in KaTeX/MathML, causing the model to attempt interpreting a heavily distorted structure, sometimes duplicating or splitting the text.

Conclusion: automated processing significantly improves the baseline state of the content and is suitable as a mass first pass, but final proofreading and minor manual corrections are still needed.

Volume — about 1,500 topics over 13 years. Considering that most of it is visually quite readable after processing and that mostly individual artifacts and complex formatting in some places require manual attention, the volume of final manual work seems acceptable.

Complete AI Pass on the Resource Corpus and Processing Economics

Following the initial pass over the topics, processing was expanded to the entire resource corpus: topics, comments, blogs, and other entities inherited from the general Resource model (historically from MODX site_content).

According to OpenRouter, the final batch amounted to approximately 12,000 AI requests, 14.1 million tokens, and around $7.99 in expenses. Almost all bulk processing was performed using Gemini 3.5 Flash Lite.

This practically matches the scale of the current archive: Topic + Comment alone yield 11,890 entities (1,349 topics and 10,541 comments), and with blogs and other types, the total volume naturally reaches about 12,000 resources.

The practical conclusion turned out to be more important than the Markdown migration itself: a full managed AI pass over a multi-year public archive costs single-digit dollars. Initially, around 20wasexpected,buttheactualcostturnedouttobeabout20 was expected, but the actual cost turned out to be about 8.

This changes content economics. Now, instead of limiting ourselves to a one-time migration, we can run specialized repeated passes across the entire corpus: normalization, classification, entity and assertion extraction, relationship building, tracking position changes, thematic retrospectives, provenance, and preparation of new publications. The cost of a full machine reading of the corpus becomes comparable to a routine technical operation rather than editorial work.

At the distribution model level, this yields another conclusion: a human can hand over the core idea to an AI, the AI can google and gather context, and then publish a managed set of materials on one or several websites. Afterwards, these materials become accessible to other AI agents and can be included in their responses via GEO/AI-search. In other words, cheap mass generation here is used not for spam, but as managed publishing infrastructure for distributing verifiable ideas and expertise.