Skip to content

Commit 90a93a2

Browse files
committed
support panel and display name
1 parent e7f05e6 commit 90a93a2

11 files changed

Lines changed: 184 additions & 21 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>uno.anahata</groupId>
88
<artifactId>gemini-java-client</artifactId>
9-
<version>1.0.7</version>
9+
<version>1.0.8-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<name>gemini-java-client</name>

src/main/java/uno/anahata/ai/Chat.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,16 @@ public String getShortId() {
538538
String sessionId = config.getSessionId();
539539
return sessionId.substring(sessionId.length() - 7);
540540
}
541+
542+
/**
543+
* Gets a human-readable display name for the chat session.
544+
* It returns the nickname if set, otherwise the shortened session ID.
545+
*
546+
* @return The display name.
547+
*/
548+
public String getDisplayName() {
549+
return StringUtils.isNotBlank(nickname) ? nickname : getShortId();
550+
}
541551

542552
/**
543553
* Calculates the current context window usage as a percentage of the token threshold.
@@ -569,4 +579,4 @@ public String getContextWindowUsageFormatted() {
569579
}
570580
return "N/A";
571581
}
572-
}
582+
}

src/main/java/uno/anahata/ai/context/session/SessionManager.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,50 @@ public String getSummaryAsString() {
183183
return sb.toString();
184184
}
185185

186+
/**
187+
* Generates a detailed, full-text dump of the entire conversation context.
188+
*
189+
* @return A Markdown string containing the detailed dump.
190+
*/
191+
public String getDetailedDump() {
192+
List<ChatMessage> historyCopy = contextManager.getContext();
193+
StringBuilder sb = new StringBuilder();
194+
sb.append("# Detailed Session Dump\n\n");
195+
sb.append("Total Messages: ").append(historyCopy.size()).append("\n\n");
196+
197+
for (ChatMessage msg : historyCopy) {
198+
Content content = msg.getContent();
199+
String role = content != null ? content.role().orElse("system") : "system";
200+
sb.append("## Message ID: ").append(msg.getSequentialId())
201+
.append(" (Role: ").append(role).append(")\n");
202+
203+
if (content != null && content.parts().isPresent()) {
204+
List<Part> parts = content.parts().get();
205+
for (int i = 0; i < parts.size(); i++) {
206+
Part part = parts.get(i);
207+
String[] desc = describePart(part);
208+
sb.append("### Part ").append(i).append(" (").append(desc[0]).append(")\n");
209+
210+
if (part.text().isPresent()) {
211+
sb.append(part.text().get()).append("\n\n");
212+
} else if (part.functionCall().isPresent()) {
213+
FunctionCall fc = part.functionCall().get();
214+
sb.append("**Function:** ").append(fc.name().get()).append("\n");
215+
sb.append("**Args:** ").append(fc.args().orElse(null)).append("\n\n");
216+
} else if (part.functionResponse().isPresent()) {
217+
FunctionResponse fr = part.functionResponse().get();
218+
sb.append("**Function:** ").append(fr.name().orElse("unknown")).append("\n");
219+
sb.append("**Response:** ").append(fr.response().orElse(null)).append("\n\n");
220+
} else {
221+
sb.append(desc[1]).append("\n\n");
222+
}
223+
}
224+
}
225+
sb.append("---\n");
226+
}
227+
return sb.toString();
228+
}
229+
186230
/**
187231
* Summarizes a single ChatMessage into Markdown table rows.
188232
*
@@ -365,4 +409,4 @@ public String[] describePart(Part p) {
365409
String finalContent = contentSummary.replace("\n", "\\n").replace("\r", "").replace("|", "\\|");
366410
return new String[]{type, finalContent};
367411
}
368-
}
412+
}

src/main/java/uno/anahata/ai/swing/ChatPanel.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class ChatPanel extends JPanel implements ContextListener, StatusListener
5353
private GeminiKeysPanel geminiKeysPanel;
5454
private ToolsPanel functionsPanel;
5555
private StatusPanel statusPanel;
56+
private SupportPanel supportPanel;
5657

5758
/**
5859
* Creates a new ChatPanel with default configuration and syntax highlighting.
@@ -180,13 +181,15 @@ private void initComponents() {
180181
contextProvidersPanel = new ContextProvidersPanel(this);
181182
geminiKeysPanel = new GeminiKeysPanel(config);
182183
functionsPanel = new ToolsPanel(chat, config);
184+
supportPanel = new SupportPanel();
183185

184186
tabbedPane = new JTabbedPane();
185187
tabbedPane.addTab("Chat", chatPanel);
186188
tabbedPane.addTab("Context Heatmap", heatmapPanel);
187189
tabbedPane.addTab("Context Providers", contextProvidersPanel);
188190
tabbedPane.addTab("Tools", functionsPanel);
189191
tabbedPane.addTab("Gemini API Keys", geminiKeysPanel);
192+
tabbedPane.addTab("Support", supportPanel);
190193

191194
tabbedPane.addChangeListener(e -> {
192195
Component selected = tabbedPane.getSelectedComponent();

src/main/java/uno/anahata/ai/swing/IconUtils.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package uno.anahata.ai.swing;
33

44
import java.awt.Image;
5+
import java.net.URL;
56
import javax.swing.ImageIcon;
67

78
/**
@@ -17,11 +18,14 @@ public class IconUtils {
1718
*/
1819
public static ImageIcon getIcon(String name) {
1920
try {
20-
ImageIcon originalIcon = new ImageIcon(IconUtils.class.getResource("/icons/" + name));
21+
URL resource = IconUtils.class.getResource("/icons/" + name);
22+
if (resource == null) {
23+
return null;
24+
}
25+
ImageIcon originalIcon = new ImageIcon(resource);
2126
Image scaledImage = originalIcon.getImage().getScaledInstance(24, 24, Image.SCALE_SMOOTH);
2227
return new ImageIcon(scaledImage);
2328
} catch (Exception e) {
24-
System.err.println("Could not load icon: " + name);
2529
return null;
2630
}
2731
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* Licensed under the Apache License, Version 2.0 */
2+
package uno.anahata.ai.swing;
3+
4+
import javax.swing.*;
5+
import java.awt.*;
6+
import java.net.URI;
7+
import lombok.extern.slf4j.Slf4j;
8+
9+
/**
10+
* A panel providing support links and community resources for users.
11+
*/
12+
@Slf4j
13+
public class SupportPanel extends JPanel {
14+
15+
public SupportPanel() {
16+
initComponents();
17+
}
18+
19+
private void initComponents() {
20+
setLayout(new GridBagLayout());
21+
GridBagConstraints gbc = new GridBagConstraints();
22+
gbc.gridx = 0;
23+
gbc.gridy = 0;
24+
gbc.anchor = GridBagConstraints.NORTHWEST;
25+
gbc.insets = new Insets(20, 20, 10, 20);
26+
27+
// Title
28+
JLabel titleLabel = new JLabel("Support & Community");
29+
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 20f));
30+
add(titleLabel, gbc);
31+
32+
gbc.gridy++;
33+
gbc.insets = new Insets(0, 20, 20, 20);
34+
gbc.fill = GridBagConstraints.HORIZONTAL;
35+
gbc.weightx = 1.0;
36+
add(new JSeparator(), gbc);
37+
38+
gbc.fill = GridBagConstraints.NONE;
39+
gbc.weightx = 0.0;
40+
gbc.insets = new Insets(5, 20, 5, 20);
41+
42+
// Discord
43+
gbc.gridy++;
44+
add(createLinkRow("Join our Discord", "https://discord.com/invite/M396BNtX",
45+
"Connect with the community and get real-time help.", "discord.png"), gbc);
46+
47+
// GitHub Issues
48+
gbc.gridy++;
49+
add(createLinkRow("Report an Issue", "https://github.com/anahata-os/gemini-java-client/issues",
50+
"Found a bug? Let us know on GitHub.", "github.png"), gbc);
51+
52+
// Email
53+
gbc.gridy++;
54+
add(createLinkRow("Email Support", "mailto:support@anahata.uno",
55+
"Send us a direct message at support@anahata.uno", "email.png"), gbc);
56+
57+
// Website
58+
gbc.gridy++;
59+
add(createLinkRow("Official Website", "https://anahata.uno/",
60+
"Learn more about the Anahata ecosystem.", "anahata.png"), gbc);
61+
62+
// Javadocs
63+
gbc.gridy++;
64+
add(createLinkRow("Browse Javadocs", "https://anahata-os.github.io/gemini-java-client/",
65+
"Technical documentation and API reference.", "javadoc.png"), gbc);
66+
67+
// Spacer to push everything to the top-left
68+
gbc.gridy++;
69+
gbc.weightx = 1.0;
70+
gbc.weighty = 1.0;
71+
add(new JPanel(), gbc);
72+
}
73+
74+
private JPanel createLinkRow(String title, String url, String description, String iconName) {
75+
JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
76+
row.setOpaque(false);
77+
78+
JButton btn = new JButton(title, IconUtils.getIcon(iconName));
79+
btn.setPreferredSize(new Dimension(200, 40));
80+
btn.setHorizontalAlignment(SwingConstants.LEFT);
81+
btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
82+
btn.addActionListener(e -> openWebpage(url));
83+
84+
JLabel descLabel = new JLabel(" - " + description);
85+
descLabel.setForeground(Color.GRAY);
86+
descLabel.setFont(descLabel.getFont().deriveFont(12f));
87+
88+
row.add(btn);
89+
row.add(descLabel);
90+
91+
return row;
92+
}
93+
94+
private void openWebpage(String url) {
95+
try {
96+
Desktop.getDesktop().browse(new URI(url));
97+
} catch (Exception e) {
98+
log.error("Failed to open URL: " + url, e);
99+
JOptionPane.showMessageDialog(this, "Could not open link: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
100+
}
101+
}
102+
}

src/main/java/uno/anahata/ai/tools/spi/LocalFiles.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ public static byte[] readBinaryFile(@AIToolParam("The absolute path of the file
7777
* @return A FileInfo object containing content and metadata.
7878
* @throws IOException if the file is not found or is a directory.
7979
*/
80-
@AIToolMethod(value = "Reads a single file and returns a FileInfo object containing its path, content, size, and last modified timestamp.", requiresApproval = false, behavior = ContextBehavior.STATEFUL_REPLACE)
80+
@AIToolMethod(value = "Reads a single text file and returns a FileInfo object containing its path, content, size, and last modified timestamp.", requiresApproval = false, behavior = ContextBehavior.STATEFUL_REPLACE)
8181
public static FileInfo readFile(
82-
@AIToolParam("The absolute path of the file to read.") String path
82+
@AIToolParam("The absolute path of the text file to read.") String path
8383
) throws IOException {
8484
Path filePath = Paths.get(path);
8585
if (!Files.exists(filePath)) {
@@ -95,7 +95,7 @@ public static FileInfo readFile(
9595
.filter(s -> s.getResourceId().equals(path))
9696
.findFirst();
9797
if (status.isPresent() && status.get().getStatus() == ResourceStatus.VALID) {
98-
throw new RuntimeException("Redundant Read: The file at " + path + " is already VALID in your context. Do not reload it.");
98+
throw new RuntimeException("Redundant Read: The text file at " + path + " is already VALID in your context (Part ID: " + status.get().getPartId() + "). Do not reload it.");
9999
}
100100

101101
String content = Files.readString(filePath);
@@ -106,17 +106,17 @@ public static FileInfo readFile(
106106
}
107107

108108
/**
109-
* Writes content to an existing file, using optimistic locking.
109+
* Writes content to an existing text file, using optimistic locking.
110110
*
111111
* @param path The absolute path to the file.
112112
* @param content The new content to write.
113113
* @param lastModified The expected last modified timestamp for safety.
114114
* @return An updated FileInfo object.
115115
* @throws IOException if a modification conflict occurs or the file is missing.
116116
*/
117-
@AIToolMethod(value = "Writes content to an existing file, but only if the file exists and has not been modified since the provided timestamp. This is a safeguard against overwriting concurrent changes. Returns the updated FileInfo object. Don not use this to create new files unse LocalFiles.createFile instead", behavior = ContextBehavior.STATEFUL_REPLACE)
117+
@AIToolMethod(value = "Writes content to an existing text file, but only if the file exists and has not been modified since the provided timestamp. This is a safeguard against overwriting concurrent changes. Returns the updated FileInfo object. Don not use this to create new files unse LocalFiles.createFile instead", behavior = ContextBehavior.STATEFUL_REPLACE)
118118
public static FileInfo writeFile(
119-
@AIToolParam("The absolute path of the file to write to.") String path,
119+
@AIToolParam("The absolute path of the text file to write to.") String path,
120120
@AIToolParam("The new content to write to the file.") String content,
121121
@AIToolParam("The expected 'last modified' timestamp of the file on disk. The write will fail if the actual timestamp is different.") long lastModified
122122
) throws IOException {
@@ -125,12 +125,12 @@ public static FileInfo writeFile(
125125
if (Files.exists(filePath)) {
126126
long currentLastModified = Files.getLastModifiedTime(filePath).toMillis();
127127
if (currentLastModified != lastModified) {
128-
throw new IOException("File modification conflict. The file at " + path
128+
throw new IOException("File modification conflict. The text file at " + path
129129
+ " was modified on disk after it was read. Expected timestamp: " + lastModified
130130
+ ", but found: " + currentLastModified);
131131
}
132132
} else if (lastModified > 0) {
133-
throw new IOException("File modification conflict. The file at " + path
133+
throw new IOException("File modification conflict. The text file at " + path
134134
+ " was expected to exist with timestamp " + lastModified + " but it has been deleted.");
135135
}
136136

@@ -140,21 +140,21 @@ public static FileInfo writeFile(
140140
}
141141

142142
/**
143-
* Creates a new file with the given content.
143+
* Creates a new text file with the given content.
144144
*
145145
* @param path The absolute path to the file.
146146
* @param content The initial content.
147147
* @return A FileInfo object for the new file.
148148
* @throws IOException if the path already exists.
149149
*/
150-
@AIToolMethod(value = "Creates a new file with the given content, creating parent directories if necessary. Throws an IOException if a file or directory already exists at the specified path.", behavior = ContextBehavior.STATEFUL_REPLACE)
150+
@AIToolMethod(value = "Creates a new text file with the given content, creating parent directories if necessary. Throws an IOException if a file or directory already exists at the specified path.", behavior = ContextBehavior.STATEFUL_REPLACE)
151151
public static FileInfo createFile(
152-
@AIToolParam("The absolute path of the file to create.") String path,
152+
@AIToolParam("The absolute path of the text file to create.") String path,
153153
@AIToolParam("The initial content to write to the file. Can be empty.") String content
154154
) throws IOException {
155155
Path filePath = Paths.get(path);
156156
if (Files.exists(filePath)) {
157-
throw new IOException("Cannot create file. Path already exists: " + path);
157+
throw new IOException("Cannot create text file. Path already exists: " + path);
158158
}
159159
if (filePath.getParent() != null) {
160160
Files.createDirectories(filePath.getParent());
@@ -164,16 +164,16 @@ public static FileInfo createFile(
164164
}
165165

166166
/**
167-
* Appends content to the end of a file.
167+
* Appends content to the end of a text file.
168168
*
169169
* @param path The absolute path to the file.
170170
* @param content The content to append.
171171
* @return An updated FileInfo object.
172172
* @throws IOException if an I/O error occurs.
173173
*/
174-
@AIToolMethod(value = "Appends content to the end of a file. Returns the updated FileInfo object.", behavior = ContextBehavior.STATEFUL_REPLACE)
174+
@AIToolMethod(value = "Appends content to the end of a text file. Returns the updated FileInfo object.", behavior = ContextBehavior.STATEFUL_REPLACE)
175175
public static FileInfo appendToFile(
176-
@AIToolParam("The absolute path of the file to append to.") String path,
176+
@AIToolParam("The absolute path of the text file to append to.") String path,
177177
@AIToolParam("The content to append.") String content
178178
) throws IOException {
179179
Files.writeString(Paths.get(path), content, java.nio.file.StandardOpenOption.APPEND, java.nio.file.StandardOpenOption.CREATE);
@@ -308,4 +308,4 @@ public static List<String> listDirectory(
308308
}).collect(Collectors.toList());
309309
}
310310
}
311-
}
311+
}
17.2 KB
Loading

src/main/resources/icons/email.png

11.9 KB
Loading
8.44 KB
Loading

0 commit comments

Comments
 (0)