One thing you can do is to define the classes and methods that you want to have, and have the LLM implement them. For tricky things, you can leave additional notes in the empty method body as to how things should be implemented.
This way you're doing the big picture thinking while having the LLM do what's it's good at, generating code within the limits of its context window and ability to reason about larger software design.
I mostly treat the LLM as an overly eager to please junior engineer that types very quickly, who can read the documentation really quickly, but also tends to write too much code and implement features that weren't asked for.
One of the good things is that the code that's generated is so low effort to generate that you can afford to throw away large chunks of it and regenerate it. With LLM assistance, I wrote some code to process a dataset, and when it was too screwy, I just deleted all of it and rewrote it a few times using different approaches until I got something that worked and was performant enough. If I had to type all of that I would've been disappointed at having to start over, and probably more hesitant to do so even if it's the right thing to do.
I've found a lot of value in this approach as well, I don't delegate any architecture decisions to LLMs. I build out the high-level and I see if the LLM can fill the gaps. I've found they are good at writing pure functions, and am good at composing them and managing state.